Приведение против использования ключевого слова as в CLR

Обратите внимание, что всякий раз, когда вы используете

regexp_like(column, '[A-Z]')

, механизм regexp Oracle будет соответствовать определенным символам из диапазона Latin-1: это относится ко всем символам, которые похожи на символы ASCII, такие как Ä-> A, Ö-> O, Ü-> U и т. Д., Так что [AZ] не то, что вы знаете из других сред, таких как, например, Perl.

Вместо того, чтобы возиться с регулярными выражениями, попробуйте изменить для тип данных NVARCHAR2 до обновления набора символов.

Другой подход: вместо того, чтобы отсекать часть содержимого полей, вы можете попробовать функцию SOUNDEX, если ваша база данных содержит только европейские символы (например, Latin-1) , Или вы просто пишете функцию, которая переводит символы из диапазона Latin-1 в аналогичные символы ASCII, такие как

  • å => a
  • ä => a
  • ö => o

, конечно, только для текстовых блоков, превышающих 4000 байт, при преобразовании в UTF-8.

371
задан Frank V 21 September 2015 в 18:06
поделиться

18 ответов

Ответ ниже строки был записан в 2008.

C# 7 представил сопоставление с образцом, которое в основном заменило as оператор, как можно теперь записать:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Отметьте это tt находится все еще в объеме после этого но не определенно присвоен. (Это определенно присвоено в if тело.) Это является немного раздражающим в некоторых случаях, поэтому если Вы действительно заботитесь о представлении самого маленького количества переменных, возможных в каждом объеме, Вы могли бы все еще хотеть использовать is сопровождаемый броском.


Я не думаю ни один из ответов до сих пор (во время запуска этого ответа!) действительно объяснили, где это стоит использовать который.

  • Не делайте этого:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }
    

    Мало того, что это проверяет дважды, но и это может проверять разные вещи, если randomObject поле, а не локальная переменная. Для возможно, "если" передать, но затем бросок для сбоя, если другой поток изменяет значение randomObject между двумя.

  • Если randomObject действительно должен быть экземпляр TargetType, т.е. если это не, который означает, что существует ошибка, затем кастинг является правильным решением. Это сразу выдает исключение, что означает, что больше работы не сделано под неправильными предположениями, и исключение правильно показывает тип ошибки.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
    
  • Если randomObject мог бы быть экземпляр TargetType и TargetType ссылочный тип, затем используйте код как это:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • Если randomObject мог бы быть экземпляр TargetType и TargetType тип значения, затем мы не можем использовать as с TargetType самостоятельно, но мы можем использовать nullable тип:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }
    

    (Примечание: в настоящее время это на самом деле медленнее, чем + бросок. Я думаю, что это более изящно и последовательно, но там мы идем.)

  • Если Вам действительно не нужно преобразованное значение, но просто необходимо знать, является ли это экземпляром TargetType, то is оператор является Вашим другом. В этом случае не имеет значения, является ли TargetType ссылочным типом или типом значения.

  • Могут быть другие случаи, включающие дженерики где is полезно (потому что Вы не можете знать, является ли T ссылочным типом или нет, таким образом, Вы не можете использовать в качестве), но они относительно неясны.

  • Я почти наверняка использовал is поскольку значение вводит случай до настоящего времени, не думая об использовании nullable типа и as вместе :)


Править: Обратите внимание, что ни одни из вышеупомянутых переговоров о производительности, кроме значения не вводят случай, где я отметил, что распаковывание к nullable типу значения на самом деле медленнее - но последовательно.

Согласно ответу naasking,-и-бросать или, и как оба с такой скоростью, как as-and-null-check с современными МОНЕТАМИ В ПЯТЬ ЦЕНТОВ, как показано кодом ниже:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

На моем ноутбуке они все выполняются приблизительно в 60 мс. Две вещи отметить:

  • Нет никакой значительной разницы между ними. (На самом деле существуют ситуации, в которых as-plus-null-check определенно медленнее. Вышеупомянутый код на самом деле осуществляет проверку типа, легкую, потому что это для запечатанного класса; если Вы проверяете на интерфейс, подсказки по балансу немного в пользу as-plus-null-check.)
  • Они все безумно быстры. Это просто не будет узким местом в Вашем коде, если Вы действительно не соберетесь делать что-либо со значениями впоследствии.

Поэтому давайте не волноваться о производительности. Давайте волноваться о правильности и непротиворечивости.

Я поддерживаю, что-и-бросать (или и как) оба небезопасны при контакте с переменными, поскольку тип значения, к которому он относится, может измениться из-за другого потока между тестом и броском. Это было бы довольно редкой ситуацией - но у меня скорее будет конвенция, которую я могу последовательно использовать.

Я также поддерживаю, что as-then-null-check дает лучшее разделение проблем. У нас есть один оператор, который делает попытку преобразования и затем одного оператора, который использует результат.-И-бросать или и как выполняет тест и затем другую попытку преобразовать значение.

Другими словами, был бы любой когда-либо писать:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

Это - вид того, что-и-бросать делает - хотя, очевидно, скорее более дешевым способом.

503
ответ дан Jon Skeet 21 September 2015 в 18:06
поделиться

Проблема ОП ограничена конкретной ситуацией. Название охватывает гораздо больше ситуаций.
Вот краткий обзор всех соответствующих ситуаций приведения, о которых я сейчас могу думать:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
0
ответ дан Tobias Knauss 21 September 2015 в 18:06
поделиться

Посмотрите эти ссылки:

они показывают вам некоторые детали и тесты производительности.

0
ответ дан juFo 21 September 2015 в 18:06
поделиться

Это зависит от того, хотите ли вы проверять наличие нуля после использования «как», или вы бы предпочли, чтобы ваше приложение выдало исключение?

Мое эмпирическое правило: если я всегда ожидаю, что переменная будет того типа, которого я ожидаю в то время, когда я хочу, я использую приведение. Если возможно, что переменная не будет приведена к тому, что я хочу, и я готов обработать пустые значения при использовании as, я буду использовать as.

0
ответ дан Darryl Braaten 21 September 2015 в 18:06
поделиться

То, что вы выбираете, сильно зависит от того, что требуется. Я предпочитаю явное приведение

IMyInterface = (IMyInterface)someobj;

, потому что если объект должен иметь тип IMyInterface, а это не так - это определенно проблема. Лучше получить ошибку как можно раньше, потому что точная ошибка будет исправлена, а не побочный эффект.

Но если вы имеете дело с методами, которые принимают object в качестве параметра, то вам необходимо проверить его точный тип перед выполнением любого кода. В таком случае было бы полезно as, чтобы вы могли избежать InvalidCastException.

0
ответ дан Oleg 21 September 2015 в 18:06
поделиться

Ключевое слово as работает так же, как и явное приведение между совместимыми ссылочными типами, с основным отличием в том, что оно не вызывает исключение в случае неудачного преобразования. Скорее, он возвращает нулевое значение в целевой переменной. Поскольку исключения являются очень дорогими с точки зрения производительности, он считается гораздо лучшим методом приведения.

0
ответ дан Cerebrus 21 September 2015 в 18:06
поделиться

Если вы используете Office PIA, нацеленные на .NET Framework 4.X, вы должны использовать ключевое слово как , иначе оно не будет компилироваться.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Кастинг в порядке при нацеливании на .NET 2.0, хотя:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

При нацеливании на .NET 4.X ошибки:

ошибка CS0656: отсутствует обязательный элемент компилятора «Microsoft.CSharp.RuntimeBinder.Binder.Convert»

ошибка CS0656: отсутствует обязательный элемент компилятора «Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create»

1
ответ дан Olivier MATROT 21 September 2015 в 18:06
поделиться

Оператор as может использоваться только для ссылочных типов, он не может быть перегружен и вернет null в случае сбоя операции. Это никогда не вызовет исключения.

Приведение может использоваться для любых совместимых типов, оно может быть перегружено, и оно выдаст исключение в случае сбоя операции.

Выбор того, что использовать, зависит от обстоятельств. Прежде всего, это вопрос того, хотите ли вы выдать исключение при неудачном преобразовании.

3
ответ дан Jeffrey L Whitledge 21 September 2015 в 18:06
поделиться

Если приведение не выполнено, ключевое слово «as» не вызывает исключение; вместо этого он устанавливает переменную в null (или в ее значение по умолчанию для типов значений).

4
ответ дан TheSmurf 21 September 2015 в 18:06
поделиться

, так как никогда не выдает исключение, если не может выполнить преобразование, возвращая ноль (, поскольку работает только с ссылочными типами). Таким образом, использование в качестве в основном эквивалентно

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

приведениям в стиле C, с другой стороны, выдает исключение, когда преобразование невозможно.

12
ответ дан Anton Gogolev 21 September 2015 в 18:06
поделиться

Не совсем ответ на ваш вопрос, но я думаю, что это важный связанный вопрос.

Если вы программируете на интерфейс, вам не нужно создавать каст. Надеюсь, эти броски очень редки. Если нет, то вам, вероятно, нужно переосмыслить некоторые из ваших интерфейсов.

10
ответ дан toad 21 September 2015 в 18:06
поделиться

Одно из более тонких различий между ними заключается в том, что ключевое слово «as» нельзя использовать для приведения, когда задействован оператор приведения:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Это не скомпилируется (хотя я думаю, что в предыдущих версиях) в последней строке, так как ключевые слова "as" не учитывают операторы приведения. Линия string cast = (string)f; работает просто отлично.

18
ответ дан Patrik Hägne 21 September 2015 в 18:06
поделиться

«as» вернет NULL, если невозможно разыграть.

приведение перед вызовет исключение.

Для представления повышение исключения обычно более затратно во времени.

69
ответ дан Frank V 21 September 2015 в 18:06
поделиться

Помимо всего того, что уже было здесь раскрыто, я только что натолкнулся на практическое различие, которое, я думаю, стоит отметить, между явным приведением

,
var x = (T) ...
,

и использованием оператора as.

.

Вот пример:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Итог: GenericCaster2 не будет работать с типами структуры. GenericCaster будет.

1
ответ дан Veverke 21 September 2015 в 18:06
поделиться

Мой ответ касается скорости только в тех случаях, когда мы не проверяем тип и не проверяем нули после приведения. Я добавил два дополнительных теста в код Джона Скита:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Результат:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Не пытайтесь сосредоточиться на скорости (как я), потому что все это очень-очень быстро .

1
ответ дан CoperNick 21 September 2015 в 18:06
поделиться

Вот еще один ответ, с некоторым сравнением IL. Рассмотрим класс:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Теперь посмотрим на IL, который создает каждый метод. Даже если операционные коды ничего не значат для вас, вы можете увидеть одно существенное отличие - вызывается isinst, за которым следует castclass в методе DirectCast. Так что два звонка вместо одного в принципе.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

Ключевое слово isinst по сравнению с каст-классом

В этом блоге есть достойное сравнение между двумя способами. Его резюме таково:

  • В прямом сравнении isinst быстрее, чем castclass (хотя и незначительно)
  • Когда необходимо выполнить проверку, чтобы убедиться, что преобразование прошло успешно, isinst был значительно быстрее чем castclass
  • Не следует использовать комбинацию isinst и castclass, так как это было намного медленнее, чем самое быстрое «безопасное» преобразование (более чем на 12% медленнее)

Я лично всегда использую как потому что он легко читается и рекомендуется командой разработчиков .NET (или Джеффри Рихтером в любом случае)

25
ответ дан Chris S 21 September 2015 в 18:06
поделиться

Это не ответ на вопрос, а комментарий к примеру кода вопроса:

Обычно вам не нужно создавать объект из, например, IMyInterface для MyClass. Самое замечательное в интерфейсах состоит в том, что если вы берете объект в качестве входных данных, который реализует интерфейс, то вам не нужно заботиться о том, какой объект вы получаете.

Если вы приведете IMyInterface к MyClass, то вы уже предполагаете, что получаете объект типа MyClass, и нет смысла использовать IMyInterface, потому что если вы передадите свой код другим классам, которые реализуют IMyInterface, это нарушит ваш код. ..

Теперь мой совет: если ваши интерфейсы хорошо спроектированы, вы можете избежать большого количества типов.

4
ответ дан f3lix 21 September 2015 в 18:06
поделиться

Пожалуйста, не обращайте внимания на совет Джона Скита, касающийся: избегайте паттерна «тест-и-каст», т.е. :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

Это микрооптимизация, которая не работает. Я выполнил некоторые реальные тесты , и тестирование-и-приведение на самом деле быстрее, чем сравнение-и-ноль-сравнение, и это также безопаснее, потому что у вас нет возможности иметь нулевую ссылку в области видимости за пределами if, если приведение не выполнено.

Если вам нужна причина, почему тестирование и приведение происходит быстрее или, по крайней мере, не медленнее, есть простая и сложная причина.

Простой: даже наивные компиляторы объединят две одинаковые операции, такие как тестирование и приведение, в один тест и ветвь. cast-and-null-test может вызвать два теста и ветвь, один для проверки типа и преобразования в null при ошибке, один для самой проверки null. По крайней мере, они оба будут оптимизированы для одного теста и ветвления, поэтому test-and-cast не будет ни медленнее, ни быстрее, чем cast-and-null-test.

Сложный: почему тестирование и приведение происходит быстрее: приведение и проверка на ноль вводят во внешнюю область видимости другую переменную, которую компилятор должен отслеживать для обеспечения жизнеспособности, и он может не иметь возможности оптимизировать эта переменная в зависимости от того, насколько сложен ваш поток управления. И наоборот, test-and-cast представляет новую переменную только в области с разделителями, поэтому компилятор знает, что переменная не работает после выхода из области, и поэтому может лучше оптимизировать распределение регистров.

Поэтому, пожалуйста, ПОЖАЛУЙСТА, пусть этот «бросок-и-ноль-тест лучше, чем тест-бросок» совет DIE. ПОЖАЛУЙСТА. Тестирование и приведение более безопасны и быстрее.

9
ответ дан naasking 21 September 2015 в 18:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: