Чи має сенс використовувати "як" замість актового фільму, навіть якщо немає нульової перевірки? [зачинено]


351

У блогах про розробку, прикладах коду в Інтернеті та (останнім часом) навіть книзі, я постійно спотикаюся про такий код:

var y = x as T;
y.SomeMethod();

або, що ще гірше:

(x as T).SomeMethod();

Це не має для мене сенсу. Якщо ви впевнені, що xце тип T, ви повинні використовувати прямий склад :(T)x . Якщо ви не впевнені, можете скористатися, asале потрібно перевірити, nullперш ніж виконати якусь операцію. Все, що вищезазначений код робить - це перетворити (корисний) InvalidCastExceptionна (марний) NullReferenceException.

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


56
Було б веселіше побачити (поцілувати як S) .SteveIsSuchA (); Але я згоден, це зловживання.
SwDevMan81

5
Це набагато крутіше, ніж писати ((T)x).SomeMethod(), чи не так? ;) (жартую, ти, звичайно, маєш рацію!)
Lucero

8
@ P Daddy Я не погоджуюся, ідеально гарне запитання (чи справді цей шаблон коду має сенс), і дуже корисний. +1 на запитання і нахмуриться будь-хто, хто проголосує за закриття.
MarkJ

9
Люцерно має рацію, ця схема кодування викликається намаганням уникнути дужок. Невичерпний після того, як потрапив у Лісп.
Ганс Пасант

13
Оптимізований код (f as T).SomeMethod()
:;

Відповіді:


252

Ваше розуміння вірно. Це звучить як намагання мікрооптимізувати для мене. Ви повинні використовувати звичайний ролик, коли впевнені у своєму роді. Окрім створення більш розумного винятку, воно також швидко провалюється. Якщо ви помиляєтесь у своєму припущенні про тип, ваша програма вийде з ладу негайно, і ви зможете побачити причину відмови негайно, а не чекати NullReferenceExceptionчи ArgumentNullExceptionабо навіть логічну помилку десь у майбутньому. Взагалі asвираз, за ​​яким nullдесь не слід перевіряти, - це запах коду.

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

if (x is SomeType)
   ((SomeType)x).SomeMethod();

яка генерує isinstінструкцію для isключового слова, і acastclass інструкцію для акторського складу (ефективно виконуючи двічі), слід використовувати:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

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


Наступне рішення: не рекомендується використовувати у виробничому коді. Якщо ви справді ненавидите таку фундаментальну конструкцію в C #, ви можете розглянути можливість переходу на VB чи іншу мову.

У випадку, якщо хтось відчайдушно ненавидить синтаксис литого, він / вона може написати метод розширення, щоб імітувати гру:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

і використовувати акуратний [?] синтаксис:

obj.To<SomeType>().SomeMethod()

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

10
@RMorrisey: Я маю на увазі принаймні один приклад: Припустимо, у вас є cacheоб'єкт, який інший потік намагається визнати недійсним, встановивши його null. У сценаріях без блокування такі речі можуть виникати.
Мехрдад Афшарі

10
is + cast достатньо, щоб викликати попередження "Не кидати зайве" з FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Це повинно бути достатньою причиною, щоб уникнути побудови.
Девід Шмітт

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

2
@MgSam Очевидно, що такий випадок використання не має сенсу для Toметоду тут, оскільки він перетворює лише всю ієрархію спадкування, яка для типів значень так чи інакше включає бокс. Звичайно, вся ідея є більш теоретичною, ніж серйозною.
Мехрдад Афшарі


40

Використання "as" не застосовує конверсії, визначені користувачем, тоді як команда використовує їх, де це доречно. Це може бути важливою відмінністю в деяких випадках.


5
Це важливо пам’ятати. Ерік Ліпперт переглядає це тут: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P тато,

5
Приємний коментар, P! Якщо ваш код залежить від цього розрізнення, я б сказав, що у вашому майбутньому є сесія налагодження пізньої ночі.
TrueWill

36

Я трохи про це написав тут:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

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

Однак є тонка різниця. (x як T). Що б не говорило () я знаю, не тільки те, що x може бути перетворено на T, але, крім того, це робить лише посилання на конверсії або розпакування, і, тим більше, що x не є нульовим. Це передає іншу інформацію, ніж ((T) x). Що б не було (), і, можливо, саме так має намір автор коду.


1
Я не погоджуюся з вашою спекулятивною захистом автора коду у вашому останньому реченні. ((T)x).Whatever() також повідомляє, що xне [задумано бути] недійсним, і я дуже сумніваюся, що автора зазвичай не хвилює, чи перетворення Tвідбувається лише з конверсіями посилань чи розпакування, або якщо для цього потрібна визначена користувачем конверсія або зміна представлення. Зрештою, якщо я визначу public static explicit operator Foo(Bar b){}, то явно мій намір Barвважатиму сумісним Foo. Дуже рідко хочеться уникнути цього перетворення.
P тато,

4
Ну, мабуть, більшість авторів коду не зробили б цього тонкого розрізнення. Я особисто міг би бути, але якби я був, я додав би коментар до цього ефекту.
Ерік Ліпперт

16

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

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

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

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


2
Дякую за посилання, це дуже цікаво. З того, як я зрозумів статтю, він насправді порівнює випадки , що не стосуються винятку. Тим не менш, стаття була написана для .net 1.1, і в коментарях вказується, що це змінилося в .net 2.0: Продуктивність тепер майже рівнозначна, при цьому префікс може бути трохи швидшим.
Хайнзі

1
У статті йдеться про те, що він порівнює випадки, що не стосуються винятку, але я робив деякі тести давно і не зміг відтворити заявлені результати, навіть із .NET 1.x. А оскільки стаття не містить код, який використовується для запуску еталону, неможливо сказати, з чим порівнюється.
Джо

"Вантажний культ" - ідеально. Перевірте "Cargo Cult Science Річарда Фейнмана" для отримання повної інформації.
Боб Денні

11

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

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


8

99% часу, коли я використовую "як", - це коли я не впевнений, що таке фактичний тип об'єкта

var x = obj as T;
if(x != null){
 //x was type T!
}

і я не хочу вловлювати явні винятки з кастингу і не робити два рази, використовуючи "є":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}

8
Ви тільки що описали правильний випадок використання для as. Що за інші 1%?
P тато,

У друкарській помилці? =) Я мав на увазі 99% часу, коли я використовую цей точний фрагмент коду, тоді як іноді я можу використовувати "як" у виклику методу чи в іншому місці.
Макс Галкін

D'oh, а як це менш корисно, ніж друга популярна відповідь ???
Макс Галкін

2
+1 Я погоджуюся, що виклик цього питання є таким же цінним, як і відповідь Рубенса Фаріаса - люди, сподіваємось, приїдуть сюди, і це стане корисним прикладом
Рубен Бартелінк,

8

Це просто тому, що людям подобається те, як це виглядає, це дуже легко читається.

Дозвольте зіткнутися з цим: оператор лиття / перетворення на мовах, схожих на C, є досить жахливим, читабельним. Мені б хотілося, щоб краще, якщо C # прийняв синтаксис Javascript:

object o = 1;
int i = int(o);

Або визначте toоператора, еквівалент лиття as:

object o = 1;
int i = o to int;

Так що ви знаєте, синтаксис JavaScript, який ви згадуєте, також дозволений у C ++.
P тато,

@PDaddy: Це не прямий 100% сумісний альтернативний синтаксис, але не призначений таким чином (конструктор X проти конвертатора)
Рубен Бартелінк,

Я вважаю за краще використовувати синтаксис C ++ dynamic_cast<>()(і подібного). Ви робите щось потворне, це повинно виглядати потворно.
Том Хотін - таклін

5

Людям asтак подобається, бо це змушує їх відчувати себе захищеними від винятків ... Як гарантія на коробці. Хлопець покладає фантазійну гарантію на коробку, тому що хоче, щоб ви відчували все тепло і підсмажене всередині. Ви думаєте, що помістите цю маленьку коробку під подушку вночі, Фея гарантії може зійти і залишити чверть, я прав Тед?

Повернення до теми ... при використанні прямого амплуа є можливість недійсного винятку з амплуа. Тож люди застосовують asяк покришне рішення до всіх своїх потреб в кастингу, оскільки as(само по собі) ніколи не кине виняток. Але найцікавіше про це - у прикладі, який ви дали(x as T).SomeMethod(); ви торгуєте недійсним винятком лиття для нульового виключення. Що обтяжує справжню проблему, коли ви бачите виняток.

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


2
"Я вважаю за краще тест" - "є", за яким слід керувати, звичайно, повільніше, ніж "як", після чого слід тест на нуль (подібно до "IDictionary.ContainsKey", після якого перенаправлення з використанням індексатора повільніше, ніж "IDictionary.TryGetValue" "). Але якщо вам це зручніше читати, без сумніву, різниця рідко є суттєвою.
Джо

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

5

Це повинно бути одним з моїх найкращих поглядів .

D&E Stroustrup та / або якесь повідомлення в блозі, якого я зараз не можу знайти, обговорює поняття toоператора, який би вирішив точку, зроблену https://stackoverflow.com/users/73070/johannes-rossel (тобто той самий синтаксис, що asі зDirectCast семантикою ).

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

Шкода, що "розумні" програмісти (часто автори книг (Juval Lowy IIRC)) обходять це шляхом зловживань asтаким чином (C ++ не пропонує as, мабуть, з цієї причини).

Навіть VB має більше узгодженості з однорідною синтаксис , який змушує вас вибрати TryCastабо DirectCastі зробити свій розум !


+1. Ви, мабуть, мали на увазі DirectCast поведінку , а не синтаксис .
Хайнзі

@Heinzi: Ta для +1. Гарна думка. Вирішив бути розумнішим і скористайтеся semanticsнатомість: P
Рубен Бартелинк,

Зважаючи на те, що C # не має претензій на сумісність із C, C ++ або Java, я відчуваю себе поглядом на деякі речі, які він запозичує з цих мов. Це виходить за рамки "я знаю, що це X", і "я знаю, що це не X, але можна представити як одне", до "я знаю, що це не X, і може не бути репрезентативним як один , але все одно дай мені X ". Я міг бачити корисність для double-to-intамплуа, який би не вдався, якщо doubleне представляв би точне значення, яке могло б вміститися в Int32, але мати (int)-1.5вихід -1 просто некрасиво.
supercat

@supercat Так, але дизайн мови непростий, хоча, як ми всі знаємо, подивіться на набір компромісів, що беруть участь у C # зведених нанівець. Єдиний відомий антидот - це регулярне читання C # у виданнях «Глибина», коли вони зустрічаються :) На щастя, я більше переймаюся розумінням нюансів F # в наші дні, і це набагато більше розуму навколо багатьох цих питань.
Рубен Бартелінк,

@RubenBartelink: Мені не зовсім зрозуміло, які саме проблеми повинні були вирішувати мінливі типи, але я думаю, що в більшості випадків було б приємніше мати MaybeValid<T>два загальнодоступних поля IsValidі Valueякий код може робити з тим, як вважає за потрібне. Це дозволило б, наприклад MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Це не тільки дозволить заощадити принаймні дві операції з копіюванням порівняно з Nullable<T>, але це також може коштувати з будь-яким типом - не Tпросто класами.
supercat

2

Я вважаю, що asключове слово можна вважати більш елегантною на вигляд версією dynamic_castC ++.


Я б сказав, що пряма роля в C # більше схожа dynamic_castна C ++.
P тато,

Я думаю, що прямолінійний ролик в C # є більш еквівалентним static_cast у C ++.
Ендрю Гарнісон

2
@Ruben Bartelink: Він повертає нуль лише вказівниками. З посиланнями, якими ви повинні користуватися, коли можливо, це кидається std::bad_cast.
P тато,

1
@Andrew Гарнізон: static_castне виконує перевірки типу виконання. Немає подібного актору для цього в C #.
P тато,

На жаль, я не знав, що ти можеш навіть використовувати касти на посиланнях, так як я лише коли-небудь використовував їх у покажчиках, але P Daddy абсолютно правильний!
Ендрю Гарнісон

1

Це, мабуть, більш популярне без технічних причин, а просто тому, що його легше читати та інтуїтивніше. (Не кажучи, що це краще просто спробувати відповісти на питання)


1

Одна з причин використання "як":

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Замість (неправильний код):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}

2
Якщо у вас є гоночні умови цього негарного, у вас виникають більші проблеми (але погодьтесь, що це приємний зразок, щоб піти з іншими, тому +1
Рубен Бартелінк,

-1, оскільки це продовжує помилку. Якщо інші потоки можуть змінювати тип obj, у вас все ще виникають проблеми. Претензія "// все ще працює" є дуже невідповідною, щоб дотримуватись істини, оскільки t буде використовуватися як вказівник на T, але він вказує на пам'ять, яка вже не є. Жодне рішення не буде працювати, коли інший потік змінить тип obj, поки триває дія (t).
Стівен К. Сталь

5
@Stephen C. Steel: Ви, здається, сильно розгублені. Зміна типу objсимволу означатиме зміну самої objзмінної, щоб вона мала посилання на інший об'єкт. Він не змінює вміст пам'яті, на яку знаходиться об'єкт, на який спочатку посилається obj. Цей оригінальний об'єкт залишився б незмінним, і tзмінна все ще міститиме посилання на нього.
P тато,


1
@ P Daddy - Я думаю, ти прав, і я помилився: якщо obj відскочив від об'єкта T до об'єкта T2, то t все одно вказував би на старий об'єкт T. Оскільки t досі посилається на старий об’єкт, він не може бути зібраний сміттям, тому старий об’єкт T залишатиметься дійсним. Мої ланцюги детектора стану гонки пройшли навчання на C ++, де подібний код із використанням динамічної передачі може бути потенційною проблемою.
Стівен К. Сталь
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.