Прямий кастинг проти "як" оператор?


709

Розглянемо наступний код:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

Яка різниця між трьома видами кастингу (гаразд, третій - це не кастинг, але ви отримуєте наміри). Якому слід віддати перевагу?


1
Не зовсім дублікат, але в попередньому запитанні також є деякі обговорення ефективності .
Невикладене

8
4-й string s = Convert.ToString(o):; 5-е: string s = $"{o}"(або еквівалентно string.Formatформа для попереднього C #)
Earth Engine

Відповіді:


833
string s = (string)o; // 1

Кидає InvalidCastException, якщо oце не a string. В іншому випадку, правонаступники oдо s, навіть якщо oє null.

string s = o as string; // 2

Призначає nullв sразі oне є stringабо , якщо oє null. З цієї причини ви не можете використовувати його з типами значень (оператор ніколи не може повернутись nullу цьому випадку). В іншому випадку, призначає oна s.

string s = o.ToString(); // 3

Викликає NullReferenceException, якщо oє null. Призначає те, що o.ToString()повертається s, незалежно від типу o.


Використовуйте 1 для більшості конверсій - це просто і просто. Я, як правило, майже ніколи не використовую 2, оскільки якщо щось не є правильним типом, я зазвичай очікую винятку. Я бачив лише необхідність у такому функціонуванні, що повертається до нуля, з погано розробленими бібліотеками, які використовують коди помилок (наприклад, повернення null = помилка, замість використання виключень).

3 не є анонсом, а лише виклик методу. Використовуйте його для того, коли вам потрібно представити рядок нестрокового об’єкта.


2
Типовим значенням ви можете призначити "null", якщо чітко визначено, наприклад: int? я; рядок s = "5"; i = s як int; // i зараз 5 s = null; i = s як int; // i зараз недійсне
Анхеледір,

3
RE: Anheledir Насправді я був би недійсним після першого дзвінка. Ви повинні використовувати явну функцію перетворення, щоб отримати значення рядка.
Гуванте

45
RE: Sander Насправді є ще одна дуже хороша причина для використання, оскільки вона спрощує код перевірки (Check for null, а не перевіряти на null та правильний тип) Це корисно, оскільки багато часу ви бажаєте кинути спеціальний один виняток. Але це дуже правда, що сліпі як дзвінки погані.
Гуванте

5
# 2 зручно для таких речей, як методи рівняння, коли ви не знаєте тип введення. Однак, як правило, так, 1 було б кращим. Хоча явно віддав перевагу над тим, що, очевидно, буде використовувати систему типів для обмеження одного типу, коли ви очікуєте лише одного :)
Calum,

6
№2 також корисний, якщо у вас є код, який може зробити щось конкретно для спеціалізованого типу, але в іншому випадку нічого не зробить.
AnthonyWJones

349
  1. string s = (string)o;Використовуйте, коли щось неодмінно має бути іншою справою.
  2. string s = o as string;Використовуйте, коли щось може бути іншою справою.
  3. string s = o.ToString(); Використовуйте, коли вам не байдуже, що це таке, але ви просто хочете використовувати доступне представлення рядків.

1
Я розумію, що ця відповідь звучить добре, але це може бути не точно.
j riv

1
Мені подобаються перші два, але я б додав "і ти впевнений, що це не нульово" до третього варіанту.
Uxonith

2
Ви можете використовувати Елвіса (?.) в ці дні, щоб уникнути турботи про це: obj? .ToString ()
Дивовижний

@Quibblesome - чудова відповідь, але мені довелося перестати думати про ваше спростування! це буквально роздуває мою думку про те, що мова існує вже понад 15 років. Таке відчуття, як вчора, коли ми всі "нерівномірно" намагалися переконати старших розрядів перейти на C #.
Griswald_911

1
@Quibblesome приємна відповідь: чи будете ви дратуватися, якщо я додам те, що 1/2/3 знаходяться так, що не потрібно прокручувати до OP. Я з ТАК би класифікував старі відповіді за голосами!
чомуз

29

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

Найбільша перевага використання прямого каста полягає в тому, що, коли він виходить з ладу, ви отримуєте InvalidCastException , який говорить вам майже про те, що пішло не так.

Якщо asоператор, якщо oце не рядок, sвстановлений на null, що зручно, якщо ви не впевнені та хочете перевірити s:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

Однак якщо ви не виконаєте цей тест, ви будете використовувати sпізніше і буде викинуто NullReferenceException . Вони, як правило, більш поширені і набагато складніше їх відстежувати, як тільки вони трапляються в дикій природі, оскільки майже кожна лінія відміняє змінну і може викинути її. З іншого боку, якщо ви намагаєтеся привласнити тип значення (будь-який примітив, або структури, такі як DateTime ), вам доведеться використовувати прямий каст - asне вийде.

У спеціальному випадку перетворення на рядок у кожного об'єкта є а ToString, тому ваш третій метод може бути нормальним, якщо oвін не є нульовим, і ви думаєте, що ToStringметод може зробити те, що вам потрібно.


2
Одна примітка - ви можете використовувати asдля типів, що зводяться до значення. IEo as DateTime не буде працювати, але o as DateTime?буде ...
Джон Гібб

Чому б не використовувати if (s is string)замість цього?
BornToCode

1
@BornToCode, для мене, в основному особисті переваги. Залежно від того, що ви робите, часто після закінчення роботи isвам доведеться все-таки знову кинути участь, тож у вас є складний, а потім і важкий склад. Чомусь asмені і нульова перевірка почувалася краще.
Блер Конрад

9

Якщо ви вже знаєте, до якого типу він може бути переданий, скористайтеся формою C-стилю:

var o = (string) iKnowThisIsAString; 

Зауважте, що лише за допомогою функції C-стиль можна виконувати примусовий явний тип.

Якщо ви не знаєте, чи це потрібний тип, і ви збираєтесь використовувати його, якщо він є, використовуйте як ключове слово:

var s = o as string;
if (s != null) return s.Replace("_","-");

//or for early return:
if (s==null) return;

Зауважте, що так само не будуть викликати жодні оператори перетворення типів. Він буде ненульовим лише в тому випадку, якщо об'єкт не є нульовим і носить вказаний тип.

Використовуйте ToString (), щоб отримати зрозуміле для людини рядкове представлення будь-якого об'єкта, навіть якщо він не може передати рядок.


3
Це цікавий маленький дім щодо операторів перетворення типів. У мене є кілька типів, для яких я створив конверсії, і тоді слідкувати за цим.
AnthonyWJones

7

Ключове слово as добре в asp.net, коли ви використовуєте метод FindControl.

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

Це означає, що ви можете оперувати введеною змінною, а не потім виводити її так, objectяк ви робили з прямим кастом:

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

Це не величезна річ, але це економить рядки коду та призначення змінних, плюс це читабельніше


6

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

Ці два еквівалентні:

Використання "як":

string s = o as string;

Використання "є":

if(o is string) 
    s = o;
else
    s = null;

Навпаки, амплуа в стилі c робиться також під час виконання, але викидає виняток, якщо амплуа неможливо зробити.

Просто додати важливий факт:

Ключове слово "як" працює лише з посиланнями. Ви не можете:

// I swear i is an int
int number = i as int;

У цих випадках вам доведеться використовувати кастинг.


Дякую, що вказали на мою помилку, ви праві. Я відредагував відповідь. ой, вибач.
Серхіо Акоста

5

2 корисний для лиття на похідний тип.

Нехай тварина:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

отримає подається з мінімальним зліпків.


2
@Chirs Moutray, це не завжди можливо, особливо якщо це бібліотека.
deceleratedcaviar

5

Відповідно до експериментів, проведених на цій сторінці: http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(Іноді на цій сторінці з’являються помилки «незаконного пересилання», тому просто оновіть, якщо вона є)

Висновок полягає в тому, що оператор "як", як правило, швидше, ніж передача. Іноді в багато разів швидше, іноді просто ледь швидше.

Я перонно, що річ "як" також є більш читабельною.

Отже, оскільки це і швидше, і "безпечніше" (виняток викидання), і, можливо, легше читати, я рекомендую весь час використовувати "як".


4

"(string) o" призведе до InvalidCastException, оскільки немає прямого складу.

"o як рядок" призведе до нульової посилання, а не до викиду.

"o.ToString ()" не є групою будь-якого типу per se, це метод, який реалізується об'єктом, і, таким чином, так чи інакше, кожним класом в .net, який "робить щось" з екземпляром клас, на який він викликається, і повертає рядок.

Не забувайте, що для перетворення в рядок також існує Convert.ToString (someType instanceOfThatType), де someType є одним із набору типів, по суті базових типів фреймворків.


3

Усі наведені відповіді хороші, якщо я можу щось додати: Для прямого використання рядкових методів та властивостей (наприклад, ToLower) ви не можете писати:

(string)o.ToLower(); // won't compile

Ви можете написати лише:

((string)o).ToLower();

але ви можете замість цього написати:

(o as string).ToLower();

asВаріант більш читається (принаймні , на мій погляд).


Конструкція (o як рядок) .ToLower () перемагає призначення оператора як. Це призведе до нульового виключення, коли o не може бути передано до рядка.
Джеймс

@james - Але хто сказав, що єдиною метою оператора в якості оператора є викид виключення, якщо передача не вдається? Якщо ви знаєте, що o - це рядок і просто хочете написати чистіший код, ви можете використовувати (o as string).ToLower()замість кількох заплутаних дужок.
BornToCode

мета а як зовсім протилежна - він не повинен викидати виняток, коли лит не виходить, він повинен повернути нуль. Скажімо, ваш o - це рядок зі значенням null, що буде потім? Підказка - ваш дзвінок ToLower не вдасться.
Джеймс

@james - Ти маєш рацію, але як бути з випадками, коли я точно знаю, що це не буде нульовим, і мені просто потрібно зробити кастинг для компілятора, щоб дозволити мені отримати доступ до методів цього об’єкта?
BornToCode

1
Ви точно можете це зробити, але це не зовсім найкраща практика, оскільки Ви не хочете покладатися на абонента або зовнішні системи, щоб переконатися, що Ваша цінність не є нульовою. Якщо ви використовуєте C # 6, тоді ви могли б зробити (o як рядок) ?. Знизити().
Джеймс

3
string s = o as string; // 2

Віддається перевагою, оскільки це дозволяє уникнути штрафного виконання подвійного кастингу.


Привіт Кріс, посилання, яке було у цій відповіді, зараз є 404 ... Я не впевнений, чи є у вас заміна, яку ви хочете поставити на своє місце?
Метт

3

Здається, вони двоє концептуально різні.

Прямий кастинг

Типи не повинні бути суто пов'язаними. Він поставляється у всіх типах ароматизаторів.

  • Спеціальне неявне / явне кастинг: Зазвичай створюється новий об'єкт.
  • Незначне значення значення: копіювання без втрати інформації.
  • Тип значення Явне: Копія та інформація можуть бути втрачені.
  • Зв'язок IS-A: Змініть тип посилання, інакше викидає виняток.
  • Той самий тип: "Кастинг є зайвим".

Таке враження, що об’єкт буде перетворений на щось інше.

AS оператор

Типи мають прямий зв’язок. Як і в:

  • Типи посилань: відносини IS-A Об'єкти завжди однакові, просто змінюються посилання.
  • Типи значення: Копіювання типів боксу та зведення нанівець.

Схоже, ви збираєтесь по-іншому поводитися з об'єктом.

Зразки та ІЛ

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }

2

Я хотів би звернути увагу на такі особливості оператора як :

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as

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


0

Намагаючись отримати рядкове представлення будь-якого (будь-якого типу), що потенційно може бути недійсним, я віддаю перевагу нижньому рядку коду. Він компактний, він викликає ToString () і правильно обробляє нулі. Якщо o недійсне, s буде містити String.Empty.

String s = String.Concat(o);

0

Оскільки про це ніхто не згадував, найбільш близький до instanceOf до Java за ключовим словом:

obj.GetType().IsInstanceOfType(otherObj)

0

Використовуйте прямий ролик, string s = (string) o;якщо в логічному контексті вашої програми stringє єдиним допустимим типом. При такому підході ви отримаєте InvalidCastExceptionта реалізуєте принцип Fail-fast . Ваша логіка буде захищена від передачі недійсного типу далі або отримання NullReferenceException, якщо використовується asоператор.

Якщо логіка очікує декількох різних типів, string s = o as string;перевірте її nullта скористайтеся isоператором.

У C # 7.0 з'явилася нова класна функція для спрощення передачі і перевірки - відповідність шаблону :

if(o is string s)
{
  // Use string variable s
}

or

switch (o)
{
  case int i:
     // Use int variable i
     break;
  case string s:
     // Use string variable s
     break;
 }

0

У C # підтримуються дві форми перетворення типів (кастинг):

|

(Резюме

• Перетворити статичний тип v в c у заданому виразі

• Можливо лише, якщо динамічний тип v є c ​​або підтип c

• Якщо ні, викидається InvalidCastException

|

v як С

• Нефатальний варіант (c) v

• Таким чином, перетворіть статичний тип v в с у заданому виразі

• Повертає нуль, якщо динамічний тип v не c, або підтип c

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.