Имеет ли смысл использовать as вместо приведения, даже если нет нулевой проверки?

В блогах разработчиков, в онлайн-примерах кода и (недавно) даже в книге я постоянно спотыкаюсь о таком коде:

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

или, что еще хуже:

(x as T).SomeMethod();

Для меня это не имеет смысла. Если вы уверены, что x относится к типу T, вам следует использовать прямое приведение: (T)x. Если вы не уверены, вы можете использовать as, но перед выполнением какой-либо операции необходимо проверить null. Все, что делает приведенный выше код, - это превращает (полезный) InvalidCastException в (бесполезный) NullReferenceException.

Я единственный, кто считает, что это вопиющее злоупотребление ключевым словом as? Или я пропустил что-то очевидное, и приведенный выше шаблон действительно имеет смысл?


person Heinzi    schedule 26.01.2010    source источник
comment
Было бы забавнее увидеть (поцелуй как S) .SteveIsSuchA (); Но я согласен, это злоупотребление.   -  person SwDevMan81    schedule 26.01.2010
comment
Это намного круче, чем писать ((T)x).SomeMethod(), не так ли? ;) (шучу, ты конечно прав!)   -  person Lucero    schedule 26.01.2010
comment
Я как бы помню, что приведение через as быстрее, чем прямое приведение - в любом случае, без нулевой проверки это бесполезно, как вы заявили.   -  person Oliver Friedrich    schedule 26.01.2010
comment
Вы абсолютно правы, и меня это не меньше беспокоит, но на самом деле это скорее напыщенная речь, чем вопрос, а этот сайт был разработан совсем не для того. Голосование закрыто.   -  person P Daddy    schedule 26.01.2010
comment
@BeowulfOF: Согласно моим тестам, as работает быстрее, чем прямое приведение, только если оно терпит неудачу, чего и следовало ожидать. И даже если бы он был быстрее, это все равно неправильно.   -  person P Daddy    schedule 26.01.2010
comment
@P Папа: И то, и другое. Поскольку я в основном разработчик VB.NET, вопрос (Или я пропустил что-то очевидное, а приведенный выше шаблон действительно имеет смысл?) Не был риторическим.   -  person Heinzi    schedule 26.01.2010
comment
@P Папа, я не согласен, совершенно хороший вопрос (действительно ли этот шаблон кода имеет смысл) и очень полезный. +1 к вопросу и хмурый взгляд всем, кто голосует за закрытие.   -  person MarkJ    schedule 26.01.2010
comment
Люцерно прав, этот образец кодирования вызван попыткой избежать скобок. Неизлечимы после контакта с Лиспом.   -  person Hans Passant    schedule 26.01.2010
comment
Оптимизированный код: (f as T).SomeMethod();)   -  person MSalters    schedule 26.01.2010
comment
Мне просто любопытно, где-нибудь задокументировано, что под капотом просто выбрасывается и закапывается InvalidCastException? Просто любопытно, откуда ты это знаешь (не спорю с тобой, просто любопытно ....)   -  person BFree    schedule 26.01.2010
comment
@BFree: Это не так. Я имел в виду следующее: если x не относится к типу T, ((T)x).SomeMethod() вызовет InvalidCastException, что более полезно, чем исключение NullReferenceException, вызванное (x as T).SomeMethod(). Исключение NullReferenceException в последнем случае не вызвано as, а вызвано (попыткой) вызова метода.   -  person Heinzi    schedule 26.01.2010
comment
@Heinzi и MarkJ: Что ж, для протокола, я проголосовал за вопрос, а также проголосовал за закрытие.   -  person P Daddy    schedule 26.01.2010
comment
Другие интересные факты о кастинге по сравнению с Джоном Скитом здесь: stackoverflow.com/questions/496096/   -  person Mikhail    schedule 21.07.2010
comment
'as' позволяет выполнить приведение, а затем проверить значение null. для статического приведения требуется проверка типа, а затем приведение. Для встроенного приведения "as" более разборчиво, следовательно, популярность. Вложенные скобки всегда труднее читать.   -  person Gusdor    schedule 11.09.2013
comment
T a = (T)x = левый, (x as T).SomeMethod(); = правый. Я доволен этим, и код чище из-за слишком большого количества скобок. кстати if (x is T) (x as T).SomeMethod(); общие строки в моем коде, так как я могу скопировать условие if (с помощью скобок), изменить i на a и использовать его для вызова метода.   -  person Bitterblue    schedule 21.03.2014
comment
Одна действительная, но отдаленная причина для записи (x as T).SomeMethod() существует, если SomeMethod является методом расширения, который разумно работает с нулевым аргументом, и если существует другой метод расширения с сигнатурой SomeMethod(this U u), где U тип переменной x. В этом случае автор кода мог намереваться использовать метод расширения, который обычно компилятор не выбирает. Конечно, подойдет и гипс. Но в этом особом случае я бы тоже смог использовать as.   -  person JBSnorro    schedule 22.03.2015
comment
@Lucero - Я серьезно отношусь к твоей шутке - я действительно думаю, что удобнее использовать (x as T).SomeMethod(), чтобы избежать лишних скобок, и он просто сообщает, что я знаю, что x is T, как Эрик Липперт написал в этом посте - так почему это так неправильно?   -  person BornToCode    schedule 31.08.2016
comment
@MSalters оптимизировал нулевой безопасный код: (x as T)?.SomeMethod();   -  person Victor    schedule 19.08.2019


Ответы (13)


Ваше понимание верно. Для меня это звучит как попытка микрооптимизации. Если вы уверены в типе, вам следует использовать обычное приведение. Помимо создания более разумного исключения, он также быстро дает сбой. Если вы ошибаетесь в своем предположении о типе, ваша программа немедленно выйдет из строя, и вы сможете сразу увидеть причину сбоя, а не ждать NullReferenceException, ArgumentNullException или даже логической ошибки в будущем. В общем, выражение as, за которым не следует null проверка, является запахом кода.

С другой стороны, если вы не уверены в приведении и ожидаете, что оно завершится ошибкой, вам следует использовать as вместо обычного приведения, заключенного в блок try-catch. Более того, рекомендуется использовать as вместо проверки типа с последующим приведением. Вместо:

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

который генерирует isinst инструкцию для is ключевое слово и castclass инструкция для приведения (эффективное выполнение приведения дважды) вы должны использовать:

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()
person mmx    schedule 26.01.2010
comment
Я думаю, что состояние гонки не имеет значения. Если вы столкнулись с этой проблемой, значит, ваш код не является потокобезопасным, и есть более надежные способы ее решения, чем использование ключевого слова as. +1 за остальную часть ответа. - person RMorrisey; 27.01.2010
comment
+1 за @RMorrisey. Если приведенный выше код имел состояние гонки, у вас почти наверняка возникли серьезные проблемы. В противном случае использование одного и двух байт-кодов в скомпилированном языке, вероятно, не будет проблемой! - person Tom Hawtin - tackline; 27.01.2010
comment
@RMorrisey: У меня есть по крайней мере один пример: предположим, у вас есть объект cache, который другой поток пытается сделать недействительным, установив для него значение null. В сценариях без блокировки такие вещи могут возникнуть. - person mmx; 27.01.2010
comment
is + cast достаточно, чтобы вызвать от FxCop предупреждение "Не приводить без необходимости": msdn. microsoft.com/en-us/library/ms182271.aspx Этого должно быть достаточно, чтобы избежать конструкции. - person David Schmitt; 27.01.2010
comment
Дело принято. Я вижу, как можно улучшить этот сценарий. В моем кодировании у меня не было случая / необходимости работать без блокировок. - person RMorrisey; 28.01.2010
comment
Вам следует избегать создания методов расширения на Object. Использование метода для типа значения приведет к тому, что он будет упакован без необходимости. - person MgSam; 28.11.2012
comment
@MgSam Очевидно, что такой вариант использования здесь не имеет смысла для метода To, поскольку он преобразуется только в иерархии наследования, что для типов значений в любом случае включает бокс. Конечно, вся идея скорее теоретическая, чем серьезная. - person mmx; 28.11.2012
comment
Конечно. Расширение To<> не будет работать с преобразованиями между предопределенными типами значений, такими как сужение целочисленного типа, а также не будет работать для пользовательских преобразований. Таким образом, он может работать только с преобразованиями ссылок, преобразованиями с упаковкой и преобразованиями с распаковкой. Поэтому не проблема, что o - это ящик. Либо уже был ящик, который собирались распаковать, либо ящик все равно собирался создать. Конечно, та же коробка используется повторно. Однако обычно методы расширения для object - это запах кода и шум в intellisense. - person Jeppe Stig Nielsen; 31.01.2015
comment
Как это связано с if (x is SomeType y) y.SomeMethod(); использованием is? - person Artog; 10.04.2018

ИМХО, as просто имеет смысл в сочетании с null проверкой:

var y = x as T;
if (y != null)
    y.SomeMethod();
person Rubens Farias    schedule 26.01.2010

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

person Larry Fix    schedule 26.01.2010
comment
Это важно помнить. Эрик Липперт рассказывает об этом здесь: blogs.msdn.com/ericlippert/archive/2009/10/08/ - person P Daddy; 26.01.2010
comment
Хороший комментарий, П! Однако, если ваш код зависит от этого различия, я бы сказал, что в будущем у вас будет сеанс ночной отладки. - person TrueWill; 26.01.2010

Я немного об этом писал здесь:

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

Я понимаю вашу точку зрения. И я согласен с его сутью: оператор приведения сообщает: «Я уверен, что этот объект можно преобразовать в этот тип, и я готов рискнуть получить исключение, если я ошибаюсь», тогда как оператор «as» сообщает «Я не уверен, что этот объект можно преобразовать в этот тип; дайте мне ноль, если я ошибаюсь».

Однако есть небольшая разница. (x as T) .Whatever () сообщает: «Я знаю не только то, что x можно преобразовать в T, но, более того, это включает только преобразования ссылок или распаковки, и, кроме того, что x не является нулевым». Это передает иную информацию, чем ((T) x) .Whatever (), и, возможно, это то, что задумал автор кода.

person Eric Lippert    schedule 26.01.2010
comment
Я не согласен с вашей умозрительной защитой автора кода в вашем последнем предложении. ((T)x).Whatever() также сообщает, что x не [предназначено быть] нулевым, и я очень сомневаюсь, что автора обычно заботит, происходит ли преобразование в T только с преобразованиями ссылки или распаковки, или если для этого требуется пользователь -определенное преобразование или преобразование с изменением представления. В конце концов, если я определю public static explicit operator Foo(Bar b){}, то я явно намерен, чтобы Bar считался совместимым с Foo. Редко, когда я хотел бы избежать этого преобразования. - person P Daddy; 27.01.2010
comment
Что ж, возможно, большинство авторов кода не будут делать этого тонкого различия. Я лично мог бы быть, но если бы был, я бы добавил комментарий по этому поводу. - person Eric Lippert; 27.01.2010

Я часто видел ссылки на эту вводящую в заблуждение статью как доказательство того, что «как» есть быстрее, чем литье.

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

Если вы потратите время на измерения, то увидите, что приведение, как и следовало ожидать, быстрее, чем "as", когда приведение выполнено успешно.

Я подозреваю, что это может быть одной из причин того, что «культ карго» использует ключевое слово as вместо приведения.

person Joe    schedule 26.01.2010
comment
Спасибо за ссылку, очень интересно. Насколько я понял из статьи, он действительно сравнивает случай без исключения. Тем не менее, статья была написана для .net 1.1, и в комментариях указывается, что это изменилось в .net 2.0: теперь производительность почти равна, а преобразование префиксов даже немного быстрее. - person Heinzi; 26.01.2010
comment
В статье действительно подразумевается, что он сравнивает случай, не являющийся исключением, но я провел несколько тестов давным-давно и не смог воспроизвести заявленные им результаты даже с .NET 1.x. А поскольку в статье не приводится код, использованный для запуска теста, невозможно сказать, что сравнивается. - person Joe; 26.01.2010
comment
Культ карго - идеально. Ознакомьтесь с Cargo Cult Science Ричардом Фейнманом для полной информации. - person Bob Denny; 02.05.2010

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

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

person Joey    schedule 26.01.2010

99% случаев, когда я использую «как», - это когда я не уверен, какой на самом деле тип объекта.

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

и я не хочу ни перехватывать явные исключения приведения, ни делать приведение дважды, используя "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
person Max Galkin    schedule 26.01.2010
comment
Вы только что описали правильный вариант использования as. Какой еще 1%? - person P Daddy; 26.01.2010
comment
В опечатке? =) Я имел в виду, что в 99% случаев я использую этот точный фрагмент кода, хотя иногда я могу использовать as в вызове метода или где-то еще. - person Max Galkin; 26.01.2010
comment
Да, а чем это менее полезно, чем второй популярный ответ ??? - person Max Galkin; 26.01.2010
comment
+1 Я согласен, что это так же ценно, как и ответ Рубенса Фариаса - люди, надеюсь, придут сюда, и это будет полезным примером - person Ruben Bartelink; 26.01.2010

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

Посмотрим правде в глаза: оператор приведения / преобразования в C-подобных языках довольно ужасен с точки зрения удобочитаемости. Я бы хотел, чтобы в C # был принят синтаксис Javascript:

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

Или определите оператор to, эквивалент as при приведении:

object o = 1;
int i = o to int;
person JulianR    schedule 26.01.2010
comment
Просто чтобы вы знали, синтаксис JavaScript, который вы упомянули, также разрешен в C ++. - person P Daddy; 26.01.2010
comment
@PDaddy: это не прямой 100% совместимый альтернативный синтаксис и не предназначен для этого (оператор X против конструктора преобразования) - person Ruben Bartelink; 26.01.2010
comment
Я бы предпочел использовать синтаксис C ++ dynamic_cast<>() (и аналогичный). Вы делаете что-то уродливое, это должно выглядеть уродливо. - person Tom Hawtin - tackline; 27.01.2010

Людям нравитсяas так сильно, потому что это позволяет им чувствовать себя в безопасности от исключений ... Как гарантия на коробку. Парень дает на коробку причудливую гарантию, потому что он хочет, чтобы внутри вас было тепло и жарко. Вы полагаете, что кладете эту коробочку себе под подушку на ночь, Фея Гарантии может прийти и оставить четвертак, я прав, Тед?

Вернемся к теме ... при использовании прямого приведения существует вероятность недопустимого исключения приведения типов. Таким образом, люди применяют as как универсальное решение для всех своих задач по кастингу, потому что as (сам по себе) никогда не вызовет исключения. Но самое забавное в том, что в приведенном вами примере (x as T).SomeMethod(); вы обмениваете исключение недопустимого приведения для исключения нулевой ссылки. Что скрывает реальную проблему, когда вы видите исключение.

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

person Bob    schedule 26.01.2010
comment
Я предпочитаю тест is - за ним следует приведение, которое, конечно, медленнее, чем за которым следует тест на null (точно так же, как IDictionary.ContainsKey с последующим разыменованием с использованием индексатора медленнее, чем IDictionary.TryGetValue). Но если вы найдете его более читаемым, без сомнения, разница редко бывает значительной. - person Joe; 26.01.2010
comment
Важное утверждение в средней части - это то, как люди применяют as в качестве универсального решения, потому что это заставляет их чувствовать себя в безопасности. - person Bob; 26.01.2010

Это должно быть одно из моих главных раздражителей < / а>.

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

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

Жалко, что «умные» программисты (часто авторы книг (Джувал Лоуи IIRC)) обходят это стороной, злоупотребляя таким образом as (C ++ не предлагает as, вероятно, по этой причине).

Даже у VB больше согласованности в единообразном синтаксисе, который заставляет вас выбирать TryCast или DirectCast и принимать решение!

person Ruben Bartelink    schedule 26.01.2010
comment
+1. Вы, вероятно, имели в виду DirectCast поведение, а не синтаксис. - person Heinzi; 26.01.2010
comment
@Heinzi: Та для +1. Хорошая точка зрения. Решил быть умным и вместо этого использовать semantics: P - person Ruben Bartelink; 26.01.2010
comment
Учитывая, что C # не претендует на совместимость с C, C ++ или Java, меня раздражают некоторые вещи, которые он заимствует из этих языков. Это выходит за рамки того, что я знаю, что это X, и я знаю, что это не X, но может быть представлен как единое целое, чтобы я знал, что это не X, и, возможно, действительно не может быть представлен как один, но дайте мне X в любом случае. Я мог видеть полезность приведения double-to-int, которое завершилось бы неудачей, если бы double не представляло точное значение, которое могло бы поместиться в Int32, но иметь (int)-1.5 yield -1 просто уродливо. - person supercat; 19.02.2014
comment
@supercat Ага, но, как мы все знаем, языковое проектирование непросто - посмотрите на набор компромиссов, связанных с значениями NULL в C #. Единственное известное противоядие - это регулярное чтение выпусков C # in Depth по мере их появления :) К счастью, в наши дни я больше озабочен пониманием нюансов F #, и это намного более разумно во многих из этих вопросов. - person Ruben Bartelink; 19.02.2014
comment
@RubenBartelink: я не совсем понимаю, какие именно проблемы должны были решать типы, допускающие значение NULL, но я думаю, что в большинстве случаев было бы лучше иметь 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>, но это также может быть полезно с любым типом _6 _ - не только с классами. - person supercat; 19.02.2014
comment
@RubenBartelink: я подозреваю, что одна из самых важных вещей в языковом дизайне (на самом деле, любой дизайн) - это готовность отказаться от упрощений в одном аспекте дизайна, которые либо создают досадные сложности в другом, либо создают то, что можно было бы легко избежать. трудности для потребителей. Иногда мне кажется, что дизайнерам слишком легко привязываться к чему-то, что кажется простым, чистым и элегантным, но на самом деле просто предотвращает то, что должно было быть незначительными осложнениями, в месте, с которым они могли бы иметь дело. Хотите поболтать? - person supercat; 20.02.2014

Я считаю, что ключевое слово as можно рассматривать как более элегантную версию dynamic_cast из C ++.

person Andrew Garrison    schedule 26.01.2010
comment
Я бы сказал, что прямое приведение в C # больше похоже на dynamic_cast в C ++. - person P Daddy; 26.01.2010
comment
Я думаю, что прямое приведение в C # более эквивалентно static_cast в C ++. - person Andrew Garrison; 26.01.2010
comment
@Ruben Bartelink: он возвращает только null с указателями. Со ссылками, которые вы должны использовать, когда это возможно, выдает std::bad_cast. - person P Daddy; 26.01.2010
comment
@ Эндрю Гаррисон: static_cast не выполняет проверку типа во время выполнения. В C # нет подобного преобразования. - person P Daddy; 26.01.2010
comment
К сожалению, я не знал, что вы даже можете использовать приведение типов для ссылок, поскольку я когда-либо использовал их только для указателей, но P Daddy абсолютно прав! - person Andrew Garrison; 27.01.2010
comment
@P Папа: забыл об этом, ты на 100% прав (Вот статья, которую я использовал для проверки) - person Ruben Bartelink; 27.01.2010

Вероятно, он более популярен без каких-либо технических причин, а просто потому, что его легче читать и он более интуитивно понятен. (Я не говорю, что от этого лучше, просто пытаясь ответить на вопрос)

person Jla    schedule 26.01.2010

Одна из причин использования «как»:

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
}
person Rauhotz    schedule 26.01.2010
comment
Если у вас есть гоночные условия, этот урод, у вас есть более серьезные проблемы (но согласитесь, что это хороший образец, чтобы пойти с другими, так что +1 - person Ruben Bartelink; 26.01.2010
comment
-1, поскольку это увековечивает заблуждение. Если другие потоки могут изменять тип obj, у вас все еще есть проблемы. Утверждение // все еще работает, очень неприятно, поскольку t будет использоваться как указатель на T, но он указывает на память, которая больше не является T. Ни одно решение не будет работать, когда другой поток изменяет тип obj, в то время как действие (t) выполняется. - person Stephen C. Steel; 26.01.2010
comment
@Stephen C. Steel: Вы, кажется, очень запутались. Изменение типа obj означало бы изменение самой переменной obj, чтобы она содержала ссылку на другой объект. Это не изменит содержимое памяти, в которой находится объект, на который изначально ссылается obj. Этот исходный объект останется неизменным, а переменная t по-прежнему будет содержать ссылку на него. - person P Daddy; 27.01.2010
comment
infoq.com/news/2010/01/CDS-Dictionary - person Ruben Bartelink; 27.01.2010
comment
@P Папа - я думаю, что ты прав, и я ошибался: если obj отскочил от объекта T к объекту T2, то t все равно будет указывать на старый объект T. Поскольку t по-прежнему ссылается на старый объект, он не может быть собран сборщиком мусора, поэтому старый объект T останется действительным. Мои схемы детектора состояния гонки были обучены на C ++, где аналогичный код с использованием dynamic_cast мог бы стать потенциальной проблемой. - person Stephen C. Steel; 27.01.2010