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


386

Під час програмування інтерфейсів я виявив, що я роблю багато кастингу або перетворення типів об'єктів.

Чи є різниця між цими двома методами перетворення? Якщо так, чи є різниця у вартості чи як це впливає на мою програму?

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 різниця.
Ніл

Відповіді:


517

Відповідь під рядком була написана у 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це поле, а не локальна змінна. Можливо, якщо "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'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
    }

    (Зауважте: наразі це насправді повільніше, ніж це + ролі . Я думаю, що це більш елегантно і стійко, але ми йдемо.)

  • Якщо ви дійсно не потрібно перетворене значення, але ви просто повинні знати , є чи він є екземпляром TargetType, то isоператор є вашим другом. У цьому випадку не має значення, чи є TargetType еталонним типом чи типом значення.

  • Можуть бути й інші випадки, пов’язані з дженериками, де isце корисно (тому що ви не можете знати, чи T є еталонним типом чи ні, тому ви не можете використовувати як), але вони відносно неясні.

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


EDIT: Зауважте, що жоден з перерахованих вище не говорить про продуктивність, окрім випадків типу значення, де я зазначив, що розпакування до типу зведеного значення насправді повільніше - але послідовно.

Відповідно до відповіді naasking, "і-кинутий", або "є" і "настільки ж швидкі, як" і "-" ", як це показано в коді нижче:

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-a виконує тест, а потім ще одну спробу перетворення значення.

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

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 може бути типовим типом, то чому використання "є" та комбінація литих вважається поганою практикою? Я маю на увазі, він генерує повільніший код, але в цьому випадку наміри є більш чіткими, ніж передача AS, наприклад "Зробіть щось, якщо targetObject є targetType", а не "Зроби щось, якщо targetObject недійсний", причому пункт AS створить непотрібну змінну поза сферою ІЧ.
Валера Колупаєв

2
@Valera: Хороші моменти, хоча я б припустив, що тест as / null є досить ідіоматичним, що намір повинен бути зрозумілий майже всім розробникам C #. Мені не подобається дублювання, що бере участь у складі "+", особисто. Насправді мені хотілося б свого роду конструкцію "як би", яка виконує обидві дії в одному. Вони йдуть разом так часто ...
Джон Скіт

2
@ Jon Skeet: вибачте за мою затримку із 128 000 000 предметів.
Behrooz

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

71

"as" поверне NULL, якщо це неможливо для передачі.

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

Для продуктивності підвищення винятку зазвичай дорожче за часом.


3
Збільшення винятків коштує дорожче, але якщо ви знаєте, що об’єкт можна подати правильно, як це вимагає більше часу через перевірку безпеки (див. Відповідь Антона). Однак вартість перевірки безпеки, я вважаю, досить невелика.

17
Вартість потенційно збільшити виняток є фактором, який слід враховувати, але часто це правильна конструкція.
Джеффрі Л Уітлідж

@panesofglass - Для еталонних типів сумісність перетворення завжди перевірятиметься під час виконання як для, так і для передачі, так що цей коефіцієнт не буде відрізняти два варіанти. (Якби це не було так, то актерський
склад

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

27

Ось ще одна відповідь, з деяким порівнянням ІЛ. Розглянемо клас:

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

Тепер погляньте на ІЛ, який виробляє кожен метод. Навіть якщо оп-коди для вас нічого не означають, ви можете побачити одну головну різницю - виклик isinst супроводжується каскадом методу 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 швидше, ніж кавказ (хоча лише незначно)
  • Коли потрібно здійснити перевірку, щоб переконатися, що перетворення було успішним, isinst виявився значно швидшим, ніж каскад
  • Поєднання isinst та casclass не слід використовувати, оскільки це було набагато повільніше, ніж найшвидша "безпечна" конверсія (на 12% повільніше)

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


Я шукав чіткого пояснення для кастингу проти як, ця відповідь робить його більш зрозумілим, оскільки він передбачає поширене проміжне мовне покрокове пояснення. Дякую!
Морзе

18

Однією з найтонших відмінностей між ними є те, що ключове слово "як" не може використовуватися для кастингу, коли задіяний оператор лиття:

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
}

Це мікрооптимізація, яка не працює. Я провів декілька реальних тестів , і тестування і передача насправді швидше порівняння "замість нуля" і "", і це також безпечніше, тому що у вас немає можливості мати нульову посилання в області за межами, якщо слід вийти невдача.

Якщо ви хочете, щоб причина тестування і передачі була швидшою, або, принаймні, не повільнішою, є проста і складна причина.

Просте: навіть наївні компілятори об'єднають дві подібні операції, такі як тест-передача, в один тест і гілку. тест типу "чи-нуль" може змусити два тести та гілку, одну для тесту типу та перетворення в нуль при відмові, а інший для самої перевірки нуля. Принаймні, вони обидва будуть оптимізовані до одного тесту та гілки, тому тестування та передача не буде ні повільнішим, ні швидшим, ніж тестування "".

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

Тож, будь ласка, нехай цей "тестовий і недійсний тест" кращий, ніж порада тестування та лиття "DIE". БУДЬ ЛАСКА. випробування та лиття є безпечнішими та швидшими.


7
@naasking: Якщо ви протестуєте двічі (відповідно до першого фрагмента), є ймовірність, що тип зміниться між двома тестами, якщо це поле чи refпараметр. Це безпечно для локальних змінних, але не для полів. Мені буде цікаво запустити ваші орієнтири, але код, який ви вказали у своєму дописі в блозі, не є повним. Я погоджуюся з тим, що не оптимізувати мікрооптимізацію, але я не думаю, що використання значення вдвічі є читабельнішим або елегантнішим, ніж використання "як" і тесту на нікчемність. (Я б напевно використовував прямий акторський склад, а не "як" після того, як є, btw.)
Джон Скіт,

5
Я також не бачу, чому це безпечніше. Я показав, чому насправді це менш безпечно. Звичайно, у вас виникла змінна область, яка може бути нульовою, але якщо ви не почнете використовувати її поза межами наступного блоку "якщо", у вас все добре. Занепокоєння щодо безпеки, яке я порушив (навколо полів, що змінюють їх значення), викликає справжнє занепокоєння з показаним кодом - ваша проблема безпеки вимагає, щоб розробники не вистачали іншого коду.
Джон Скіт

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

1
Дійсно, якщо прив'язка не локальна, є ймовірність помилки TOCTTOU (час перевірки до часу використання), тож хороший момент. Щодо того, чому це безпечніше, я працюю з багатьма молодшими розробниками, які люблять чомусь використовувати місцевих жителів. Таким чином, "cast-and-null" є дуже реальним загрозою для мого досвіду, і я ніколи не стикався з ситуацією TOCTTOU, оскільки не розробляю свій код таким чином. Що стосується швидкості тестування виконання, вона навіть швидша, ніж віртуальна відправка [1]! Re: код, я побачу, чи зможу я знайти джерело для тесту на лиття. [1] вищийlogics.blogspot.com/2008/10/…
naasking

1
@naasking: Я ніколи не стикався з локальною проблемою повторного використання - але я б сказав, що її легше помітити в огляді коду, ніж більш тонку помилку TOCTTOU. Варто також зазначити, що я щойно перезапустив власну перевірку орієнтирів на інтерфейси замість герметичного класу, і це підказує продуктивність на користь як перевірки з нуля, але, як я вже говорив, продуктивність не є Тому я не вибрав би конкретний підхід тут.
Джон Скіт

4

Якщо випуск не вдається, ключове слово "як" не кидає винятку; замість цього вона встановлює нульову (або її значення за замовчуванням для типів значень).


3
Немає значень за замовчуванням для типів значень. Оскільки не можна використовувати для кастингу типів значень.
Патрік Хягне

2
Ключове слово "як" фактично не працює на типи значень, тому воно завжди встановлюється на нуль.
Ерік ван Бракель

4

Це не відповідь на питання, а коментар до прикладу коду питання:

Зазвичай вам не доведеться передавати об’єкт, наприклад, IMyInterface в MyClass. Чудова річ у інтерфейсах полягає в тому, що якщо ви берете об’єкт як вхід, який реалізує інтерфейс, то вам не доведеться дбати, який тип об’єкта ви отримуєте.

Якщо ви передаєте IMyInterface в MyClass, ви вже припускаєте, що ви отримуєте об’єкт типу MyClass, і не має сенсу використовувати IMyInterface, тому що якщо ви подаєте свій код з іншими класами, які реалізують IMyInterface, він порушить ваш код ...

Тепер, моя порада: якщо ваші інтерфейси добре розроблені, ви можете уникнути великої кількості наборів текстів.


3

asОператор може бути використаний тільки на посилальні типи, не можуть бути перевантажені, і він повернетьсяnull , якщо операція не виконується. Це ніколи не кине виняток.

Кастинг можна використовувати на будь-яких сумісних типах, він може бути перевантажений, і він викине виняток, якщо операція не вдасться.

Вибір, який використовувати, залежить від обставин. Перш за все, справа в тому, чи потрібно викинути виняток за невдалої конверсії.


1
"as" може також використовуватися для типів, що змінюються на значення, що забезпечує цікавий зразок. Дивіться мою відповідь щодо коду.
Джон Скіт

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

Якщо ви використовуєте службові 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'


0

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


Не те саме, як один викликає CastClass, а інший викликає IsInst у коді IL.
Дженікс

0

Те, що ви обираєте, сильно залежить від того, що потрібно. Я віддаю перевагу явного кастингу

IMyInterface = (IMyInterface)someobj;

адже якщо об'єкт повинен бути типу IMyInterface, а це не так - це, безумовно, проблема. Помилку краще отримувати якомога раніше, оскільки точна помилка буде виправлена ​​замість фіксації її побічного ефекту.

Але якщо ви маєте справу з методами, які приймає objectяк параметр, вам потрібно перевірити його точний тип перед виконанням будь-якого коду. У такому випадку asбуло б корисно, щоб уникнути InvalidCastException.


0

Це залежить від того, чи хочете ви перевірити наявність нуля після використання "як", чи хочете, щоб ваш додаток кинув виняток?

Моє правило - якщо я завжди очікую, що змінна буде такою, яку я очікую в той момент, коли я хочу використовувати кастинг. Якщо можливо, що змінна не приведе до того, що я хочу, і я готова обробляти нулі з використання as, я буду використовувати як.



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.