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

325

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

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

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

(x as T).SomeMethod();

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

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

  • 56
    Было бы смешнее видеть (целовать как S) .SteveIsSuchA (); Но я согласен, это злоупотребление.
  • 5
    Это намного круче, чем писать ((T)x).SomeMethod() , не так ли? ;) (шучу, ты прав конечно!)
Показать ещё 15 комментариев
Теги:
type-conversion
casting

13 ответов

245
Лучший ответ

Ваше понимание верно. Это похоже на попытку микро-оптимизации для меня. Вы должны использовать обычный бросок, когда вы уверены в типе. Помимо создания более разумного исключения, он также быстро выходит из строя. Если вы ошибаетесь в своем предположении о типе, ваша программа немедленно сработает, и вы сможете сразу увидеть причину сбоя, а не ждать 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 прошла успешно и завершилась неудачей в литой строке. Последний метод не подвержен этой ошибке.


Следующее решение не рекомендуется для использования в производственном коде. Если вы действительно ненавидите такую ​​фундаментальную конструкцию в С#, вы можете подумать о переходе на 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
    Я думаю, что состояние гонки не имеет значения. Если вы столкнулись с этой проблемой, то ваш код не является поточно-ориентированным, и есть более надежные способы его решения, чем использование ключевого слова «as». +1 для остальной части ответа.
  • 0
    +1 за @RMorrisey. Если в приведенном выше коде было условие гонки, у вас почти наверняка есть серьезные проблемы В противном случае, один против двух байт-кодов в «скомпилированном языке», вероятно, не будет проблемой!
Показать ещё 7 комментариев
43

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

var y = x as T;
if (y != null)
    y.SomeMethod();
39

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

  • 5
    Это важно помнить. Эрик Липперт рассказывает об этом здесь: blogs.msdn.com/ericlippert/archive/2009/10/08/…
  • 5
    Хороший комментарий, P! Если ваш код зависит от этого различия, я бы сказал, что в будущем у вас будет поздняя отладочная сессия.
37

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

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

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

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

  • 1
    Я не согласен с вашей умозрительной защитой автора кода в вашем последнем предложении. ((T)x).Whatever() также ((T)x).Whatever() что x не является [предназначенным быть] нулевым, и я очень сомневаюсь, что автору, как правило, будет интересно, происходит ли преобразование в T только с ссылочными или распакованными преобразованиями, или требуется определяемое пользователем или изменяющее представление преобразование. В конце концов, если я определю public static explicit operator Foo(Bar b){} , то я намерен считать Bar совместимым с Foo . Я редко хотел бы избежать этого преобразования.
  • 4
    Что ж, возможно, большинство авторов кода не будут делать этого тонкого различия. Лично я мог бы быть, но если бы я был, я бы добавил комментарий на этот счет.
16

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

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

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

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

  • 2
    Спасибо за ссылку, это очень интересно. От того, как я понял статью, он делает сравнение случая без исключения. Тем не менее, статья была написана для .net 1.1, и в комментариях отмечается, что это изменилось в .net 2.0: производительность теперь почти одинакова, при этом префикс преобразуется даже немного быстрее.
  • 1
    В статье подразумевается, что он сравнивает случай, не являющийся исключением, но я давным-давно провел некоторые тесты и не смог воспроизвести его заявленные результаты, даже с .NET 1.x. А поскольку статья не содержит кода, используемого для запуска теста, невозможно сказать, что сравнивается.
Показать ещё 1 комментарий
11

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

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

8

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

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

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

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

object o = 1;
int i = o to int;
  • 0
    Как вы знаете, синтаксис JavaScript, который вы упоминаете, также разрешен в C ++.
  • 0
    @Padaddy: Это не 100% совместимый альтернативный синтаксис, хотя и не предназначенный для этого (оператор X против конструктора преобразования)
Показать ещё 1 комментарий
8

В 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; 
}
  • 8
    Вы только что описали правильный вариант использования as . Какой еще 1%?
  • 0
    В опечатке? =) Я имел в виду 99% времени, когда я использую этот точный фрагмент кода, хотя иногда я могу использовать «как» в вызове метода или в другом месте.
Показать ещё 2 комментария
5

Это должно быть одним из моих топ-писем.

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

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

Жаль, что "умные" программисты (часто авторы книг (Juval Lowy IIRC)) обошли это, злоупотребляя as таким образом (С++ не предлагает as, вероятно, по этой причине).

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

  • 0
    +1. Вы, вероятно, имели в виду поведение DirectCast , а не синтаксис .
  • 0
    @Heinzi: Та за +1. Хорошая точка зрения. Решил быть умным и использовать вместо этого semantics : P
Показать ещё 4 комментария
5

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

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

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

  • 2
    «Я предпочитаю is is» - «is», за которым следует приведение, которое, конечно, медленнее, чем «as», за которым следует проверка на нулевое значение (точно так же, как «IDictionary.ContainsKey» с последующим разыменованием с использованием индексатора медленнее, чем «IDictionary.TryGetValue». «). Но если вы найдете его более читабельным, без сомнения, разница редко бывает значительной.
  • 0
    Важное заявление в средней части, как люди применяют as качестве решения офсетного , потому что это заставляет их чувствовать себя в безопасности.
2

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

  • 0
    Я бы сказал, что прямое приведение в C # больше похоже на dynamic_cast в C ++.
  • 0
    я думаю, что прямое приведение в C # более эквивалентно static_cast в C ++.
Показать ещё 4 комментария
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
  • 0
    -1 как это увековечивает ошибку. Если другие потоки могут изменять тип объекта, у вас все еще есть проблемы. Утверждение «// все еще работает» очень неудобно для выполнения, поскольку t будет использоваться как указатель на T, но оно указывает на память, которая больше не является T. Ни одно из решений не будет работать, когда другой поток изменит тип obj, пока выполняется действие (t).
Показать ещё 3 комментария
1

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

Сообщество Overcoder
Наверх
Меню