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


387

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

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

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);
    }
}

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


Не могли бы вы добавить небольшой пример того, почему вы в первую очередь используете приведение к вопросу, или, возможно, начать новый? Меня интересует, почему вам нужен актерский состав только для юнит-тестирования. Я думаю, что это выходит за рамки этого вопроса, хотя.
Эрик ван Бракел

2
Я, вероятно, могу изменить свой юнит-тест, чтобы избежать этой необходимости. По сути, это сводится к тому, что у меня есть свойство конкретного объекта, которого нет в интерфейсе. Мне нужно установить это свойство, но в реальной жизни это свойство было бы установлено другими способами. Это отвечает на ваш вопрос?
Фрэнк V

Как Patrik Hägne проницательно указывает ниже, есть IS разница.
Нил

Ответы:


520

Ответ под строкой был написан в 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самим собой, но мы можем использовать обнуляемый тип:

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

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

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

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

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


РЕДАКТИРОВАТЬ: Обратите внимание, что ни один из вышеперечисленных не говорит о производительности, кроме случая типа значения, где я заметил, что распаковка в тип значения, допускающий значение NULL, на самом деле медленнее - но согласованно.

В соответствии с ответом Нааскинга, is-and-cast или is-and-as являются такими же быстрыми, как и нулевая проверка с современными 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 мс. Обратите внимание на две вещи:

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

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

Я утверждаю, что 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
}

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


7
Вот стоимость того, как / как / литье в терминах IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
плинтус

3
В случае, если targetObject может быть целевым типом, почему использование комбинации «is» и cast считается плохой практикой? Я имею в виду, что он генерирует более медленный код, но в этом случае намерения более ясны, чем приведение AS, например «Делать что-то, если targetObject is targetType», а не «Делать что-то, если targetObject равен null», более того, предложение AS создаст ненужную переменную вне области видимости.
Валера Колупаев

2
@Valera: Хорошие моменты, хотя я бы предположил, что тест as / null достаточно идиоматичен, поэтому намерение должно быть ясным почти для всех разработчиков C #. Лично мне не нравится дублирование, участвующее в касте is +. Я бы на самом деле хотел что-то вроде конструкции «как будто», которая выполняет оба действия в одном. Они ходят вместе так часто ...
Джон Скит

2
@Jon Skeet: извините за опоздание. Is And Cast: 2135, Is And As: 2145, As And null check: 1961, спецификации: ОС: Windows Seven, CPU: i5-520M, 4 ГБ оперативной памяти DDR3 1033, тест на массив из 128 000 000 предметов.
Behrooz

2
С C # 7 вы можете сделать: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}или использовать switch/ case смотреть документы
WerWet

72

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

кастинг до поднимет исключение.

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


3
Повышение исключений обходится дороже, но если вы знаете, что объект может быть приведен правильно, так как требует больше времени из-за проверки безопасности (см. Ответ Антона). Однако стоимость проверки безопасности, я считаю, довольно мала.

17
Стоимость потенциального создания исключения - это фактор, который необходимо учитывать, но зачастую это правильный дизайн.
Джеффри Л Уитледж

@panesofglass - для ссылочных типов совместимость преобразования всегда будет проверяться во время выполнения как для as, так и для cast, так что коэффициент не будет различать эти два параметра. (Если бы это было не так, то актеры не могли бы поднять исключение.)
Джеффри Л. Уитледж

4
@Frank - если вам требуется, например, использовать коллекцию предварительных обобщений, а для метода в вашем API требуется список сотрудников, а какой-то джокер вместо этого передает список продуктов, тогда недопустимое исключение приведения может подать сигнал нарушение требований интерфейса.
Джеффри Л Уитледж

27

Вот еще один ответ, с некоторым сравнением 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 (или Джеффри Рихтер в любом случае)


Я искал четкое объяснение кастинга против, так как этот ответ делает его более понятным, поскольку включает в себя общий промежуточный язык пошаговое объяснение. Спасибо!
Морзе

18

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

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


12

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

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

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


4
Эквивалентно, да, но не то же самое. Это создает гораздо больше кода, чем как.
постамент

10

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

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


Пока что кастинг в основном был необходим для моего юнит-тестирования, но спасибо, что подняли его. Я буду помнить об этом, пока работаю над этим.
Фрэнк V

Согласившись с жабой, мне также любопытно, почему аспект модульного тестирования имеет отношение к кастингу для вас @Frank V. Там, где есть необходимость в кастинге, часто возникает необходимость в редизайне или рефакторинге, так как он предполагает, что вы пытаетесь рожать различные проблемы, где они должны решаться по-разному.
Сенатор

@TheSenator Этот вопрос уже более 3 лет, поэтому я не помню. Но я, вероятно, агрессивно использовал интерфейсы даже при модульном тестировании. Возможно, потому что я использовал фабричный шаблон и не имел доступа к общедоступному конструктору для целевых объектов для тестирования.
Фрэнк V

9

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

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

Идея, что это стоит больше, чем приведение и нулевой тест, является МИФОМ :

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

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

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

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

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

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


7
@naasking: если вы тестируете дважды (согласно вашему первому фрагменту), есть вероятность, что тип изменится между двумя тестами, если это поле или refпараметр. Это безопасно для локальных переменных, но не для полей. Мне было бы интересно запустить ваши тесты, но код, который вы дали в своем блоге, неполон. Я согласен с не микрооптимизацией, но я не думаю, что использование значения дважды более удобочитаемо или элегантно, чем использование «как» и тест на недействительность. (Между прочим, я бы определенно использовал прямой бросок, а не «как» после того, как есть.)
Джон Скит

5
Я также не понимаю, почему это безопаснее. Я показал, почему это на самом деле менее безопасно. Конечно, вы получите переменную в области видимости, которая может быть нулевой, но если только вы не начнете использовать ее вне области действия последующего блока «if», все в порядке. Озабоченность безопасностью, которую я затронул (вокруг полей, меняющих свое значение), является подлинной заботой о показанном коде - ваша забота о безопасности требует, чтобы разработчики проявляли слабость в другом коде.
Джон Скит

1
+1 за указание, что / cast или as / cast не медленнее в реальности, учтите. Проведя полный тест самостоятельно, я могу подтвердить, что, насколько я вижу, это не имеет значения - и, честно говоря, вы можете выполнить ошеломляющее количество бросков за очень короткое время. Обновлю мой ответ полным кодом.
Джон Скит

1
В самом деле, если привязка не является локальной, существует вероятность ошибки TOCTTOU (время проверки на время использования), так что это хороший момент. Что касается того, почему это безопаснее, я работаю со многими начинающими разработчиками, которые по каким-то причинам любят повторное использование местных пользователей. Таким образом, в моем опыте приведение к нулю - это очень реальная опасность, и я никогда не сталкивался с ситуацией TOCTTOU, поскольку не создавал свой код таким образом. Что касается скорости тестирования во время выполнения, она даже быстрее, чем виртуальная диспетчеризация [1]! Re: код, я посмотрю, смогу ли я найти источник для броска теста. [1] higherlogics.blogspot.com/2008/10/...
naasking

1
@naasking: я никогда не сталкивался с проблемой локального повторного использования - но я бы сказал, что в обзоре кода легче обнаружить, чем более тонкую ошибку TOCTTOU. Стоит также отметить, что я просто перезапустил собственную проверку производительности интерфейсов, а не запечатанного класса, и это дает преимущество производительности в пользу проверки «как тогда ноль» ... но, как я уже сказал, производительность не Поэтому я бы выбрал какой-то конкретный подход здесь.
Джон Скит

4

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


3
Нет значений по умолчанию для типов значений. Как нельзя использовать для приведения типов значения.
Патрик Хагне

2
Ключевое слово «as» фактически не работает с типами значений, поэтому оно всегда имеет значение null.
Эрик ван Бракель

4

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

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

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

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


3

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

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

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


1
'as' также может использоваться в типах значений, допускающих значение NULL, что обеспечивает интересный шаблон. Смотрите мой ответ для кода.
Джон Скит

1

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

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

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


Аналогично, в моем тестировании я обнаружил, что asпреобразование (без проверки ошибок) выполняется примерно на 1-3% быстрее, чем приведение (около 540 мс против 550 мс на 100 млн итераций). Ни один не сделает или сломает ваше заявление.
Palswim

1

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

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

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

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'


0

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


Не то же самое, что один вызывает CastClass, а другой вызывает IsInst в коде IL.
Дженикс

0

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

IMyInterface = (IMyInterface)someobj;

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

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


0

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

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



0

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

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
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.