Кастинг против ключевого слова "как" в CLR

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

Есть ли разница между этими двумя методами преобразования? Если да, есть ли разница в стоимости или как это влияет на мою программу?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Кроме того, что такое "вообще" предпочтительный метод?

Ответ 1

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

С# 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 - поле, а не локальная переменная. Возможно, что "if" пройдет, а затем приведение к отказу, если другой поток изменит значение 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 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, но мы можем использовать тип с нулевым значением:

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

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

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

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

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


EDIT: Обратите внимание, что ни одно из вышеперечисленных вопросов о производительности, отличное от случая типа значения, где я заметил, что unboxing для типа значения NULL на самом деле медленнее, но непротиворечиво.

В соответствии с ответом на наследование, "is-and-cast" или "is-and-as", как и как-и-null-check с современными JIT, как показано ниже:

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.)
  • Они все безумно быстро. Это просто не будет узким местом в вашем коде, если вы действительно ничего не собираетесь делать со значениями после этого.

Так что не беспокойтесь о производительности. Позвольте беспокоиться о правильности и последовательности.

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

Я также утверждаю, что проверка "как-то-нуль" дает лучшее разделение проблем. У нас есть одно выражение, которое пытается преобразовать, а затем один оператор, который использует результат. Функция is-and-cast или is-and-as выполняет тест, а затем другую попытку преобразования значения.

Иными словами, кто-нибудь когда-нибудь напишет:

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

Это то, что делают-и-литые, хотя, очевидно, довольно дешево.

Ответ 2

", поскольку" возвращает NULL, если это невозможно сделать.

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

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

Ответ 3

Вот еще один ответ, с некоторым сравнением 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, который производит каждый метод. Даже если коды op ничего не означают для вас, вы можете увидеть одно существенное различие - 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 против класса cast

Это сообщение в блоге имеет достойное сравнение между двумя способами его выполнения. Его резюме:

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

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

Ответ 4

Одно из самых тонких различий между ними заключается в том, что ключевое слово "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; работает отлично, хотя.

Ответ 5

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

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;
С другой стороны,

C-style casts генерирует исключение, если невозможно преобразование.

Ответ 6

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

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

Ответ 7

Пожалуйста, игнорируйте совет Джона Скита, повторите: избегайте тестового шаблона, т.е.:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

Идея, что это стоит больше, чем листинг и нулевой тест, - это MYTH:

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

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

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

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

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

Итак, пожалуйста, ПОЖАЛУЙСТА, пусть это "литье и нуль-тест лучше, чем тест-литье". ПОЖАЛУЙСТА. test-and-cast безопаснее и быстрее.

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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

Ответ 11

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

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

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

Ответ 12

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

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 будет.

Ответ 13

Если вы используете 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'

Ответ 14

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

Ответ 15

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

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

Ответ 16

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

IMyInterface = (IMyInterface)someobj;

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

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

Ответ 18

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

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
}