as
, который пытается преобразовать объект и в случае успеха возвращает результат, в случае неудачи null
; или использовать оператор преобразования.Какой из этих вариантов выбрать, когда нужно немедленно воспользоваться результатом преобразования?
// вариант 1
var thing = GetCurrentItem();
var foo = thing as Foo;
foo.DoSomething();
// вариант 2
var thing = GetCurrentItem();
var foo = (Foo)thing;
foo.DoSomething();
Теперь предположим, что объект
thing
не является типом Foo
. Оба варианта отработают некорректно, однако сделают они это по-разному.В первой варианте отладчик выдаст исключение
NullReferenceException
в методе foo.DoSomething()
, и дамп сбоя подтвердит, что переменная foo
является null
. Однако этого может и не быть в дампе сбоя. Возможно, дамп сбоя захватывает лишь параметры, которые участвовали в выражении, которое в свою очередь привело к исключению. Или может быть переменная thing
попадёт в сборщик мусора. Вы не можете определить где проблема когда GetCurrentItem
возвращает null
или GetCurrentItem
возвращает объект другого типа, отлично от типа Foo
. И что это если не Foo
? Во втором варианте также могут возникнуть ошибки. Если объект
thing
является null
, при вызове метода foo.DoSomething()
ты получишь исключение NullReferenceException
. Однако, если объект thing
имеет другой тип, сбой произойдет в точке преобразования типов и выдаст исключение InvalidCastException
. Если повезёт, отладчик покажет что именно нельзя преобразовать. Если не сильно повезёт, можно будет определить где произошел сбой по типу выданного исключения. Задание: Оба варианта ниже функционально эквивалентны. Какой будет проще отладить?
// вариант 1 collection.FirstOrDefault().DoSomething();
// вариант 2 collection.First().DoSomething();
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (41)
piranuy
27.07.2017 17:34-6А вы отсутствие объектов в коллекциях всегда через отлов Exception определяете что-ли? Вышеприведенное использование firstordefault и AS не соответствует их реальному назначению.
piranuy
27.07.2017 17:35-5И вообще зачем тут Linq приплетать, если вы там даже условие не прописываете. Где вариант с [0]?
alexeystarchikov
27.07.2017 17:40+1NullReferenceException может быть и из-за того, то самой коллекции не существует. Именно поэтому второй вариант предпочтительней. Особенно в реалиях UWP, где callstack в большинстве случаев весьма неинформативен
ad1Dima
28.07.2017 06:41+1Вы же знаете про StackParser из https://github.com/dotnet/corefx-tools/?
alexeystarchikov
28.07.2017 09:51+1Проблема в том, что очень часто StackTrace просто пустой. Я говорю не о 100%, а об ошибках, которые не покрыты локальными try..catch, а те, что можно отловить уже через Application.UnhandledException
ad1Dima
28.07.2017 09:54+1Чаще всего это деббагер глючит и если вывести ошибку в мессаджбокс, то трейс магическим образом появляется
alexeystarchikov
28.07.2017 10:14+1Какой еще дебагер в Release? Я говорю о том, что в Exception.StackTrace, который приходит в Application.UnhandledException, часто бывает просто пустой
ad1Dima
28.07.2017 10:18+1А вот это уже же странно. В логи трейс обычно попадает. Видно есть в вашем коде что-то конкретное, что трет трейс.
strayker1206
28.07.2017 10:28+1Данная проблема действительно существует. Возможно, заметка Вам поможет:
ad1Dima
28.07.2017 10:36+1тут на самом деле может быть ситуация, что проблема вообще в стороннем нативном коде…
alexeystarchikov
28.07.2017 12:38+1Спасибо. Заметка то что нужно. Надо будет попробовать решение «на досуге»
DrFdooch
27.07.2017 17:40+1Это, извините, очень слабая статья. Приведенные примеры не про отладку — они про использование подходящих инструкций. Если вы используете методы, не выбрасывающие исключение — то проверяйте возвращаемый результат (если он может быть невалидным). Я понимаю, корпоративный блог и всё такое, но зачем настолько куцые материалы переводить?
А вот про именно специфичные для отладки/инвестигирования кейсы было интересно почитать, даже сходу ничего не приходит в голову.
aosja
27.07.2017 17:40+6// вариант 3 switch (thing) { case Foo foo: foo.DoSomething(); ..... }
Из разряда «из двух неправильных вариантов выберите менее неправильный»blanabrother
27.07.2017 18:47+2В Вашем варианте тогда нужен default обработчик, где Вы что будете делать? Скорее всего надо кидать исключение, если ни один из case не заматчил объект. Или просто проглотите и ничего не сделаете?
aosja
27.07.2017 22:38+2Да, исключение. И, возможно, более осмысленное, нежели NRE или ошибка приведения типов. Но, надо отдать должное автору, контекста в данном вопросе — ноль. Так что, спорить что лучше я не буду :)
mayorovp
28.07.2017 05:50+2Что может быть осмысленнее ошибки приведения типа в ситуации когда у объекта неправильный тип?
Mispon
27.07.2017 18:08-1Предпочтительнее тот вариант, который больше удовлетворяет цель. Если мы, например, для того, чтобы отобразить на главной странице незначительную ерунду используем .Firts() и весь веб падает в случае отсутствии этой ерунды, то аргумент "зато проще отлаживать" явно не катит.
unsafePtr
27.07.2017 19:11+1First, FirstOrDefault, Single, SingleOrDefault очень хорошо улучшают читаемость кода. Согласен что надо искать компромисс между читаемостью и производительнотю, и шарпам это очень хорошо удаётся. Кстати говоря, если метод не обязательно должен быть исполнен мы можем воспользоваться элвис оператором ?.
Serg046
28.07.2017 00:49+2В приведенном примере, в случае отсутствия элемента в кол-ции, FirstOrDefault() все равно не спасает.
Тогда уж его хотя бы нужно переписать:
collection.FirstOrDefault()?.DoSomething();
Так что вариант 1 просто вносит дополнительную ненужную проверку и портит колстек в случае ошибки.
Andikki
27.07.2017 21:06+2Глупость какая-то. В обоих случаях первый вариант является просто плохим кодом. Когда пишешь
thing as Foo
илиcollection.FirstOrDefault()
, нужно учесть вероятность полученияnull
— проверить результат наnull
, использовать оператор?.
и т. п.
Второй вариант допустим, когда уверен, что в нормальных условиях этот код сработает — что
thing
— это действительно всегдаFoo
, и что в коллекции обязательно есть как минимум один элемент.
Вопрос выбора между равнозначными вариантами не стоит.
stranger777
27.07.2017 21:14+4Сначала ты работаешь на имя — потом имя работает на тебя.
Представим себе, что ровно тот же текст, байт в байт, только без картинко-текста (пора вводить в язык новое слово — pictext, оно же пиктекст) с логотипом Майкрософт, Вася Пупкин написал в песочницу. Вопрос: пустят ли Васю Пупкина на Хабр?
Ну, нельзя так… а проще, конечно, второй. Если что. Это можно ответить даже не зная C#.
oleg2
27.07.2017 22:11+1Конечно, второй)
Хотя здесь все-таки не NullReferenceException, а ArgumentNullException, First будет как минимум более выразительным.
morozyan
27.07.2017 22:12+1С первого взгляда, второй проще отладить, но если углубиться в задачу, то все равно второй вариант выигрывает:
FirstOrDefaultAlex_ME
28.07.2017 01:58Смотря для чего лучше.
С точки зрения отладки, второй лучше — если мы забыли проверку, он вывалится в одном месте, а не в рандомном, где мы используем результат. Но с точки зрения производительности лучше просто проверить результат на null.
var item = collection.FirstOrDefault().DoSomething(); if(item == null) { // обработать }
Atomosk
28.07.2017 08:56+1Еще никогда у меня не было выбора между двумя вариантами кода, вариантов всегда очень много. Пока есть дженерики + экстеншны или наследование первые 2 варианта примера никогда не придется писать.
Оба варианта ниже функционально эквивалентны.
В общем случае нет.
gset
28.07.2017 10:01+1Как по мне, то решать надо исходя из ситуации. Если наличие null в место объекта не повредит программе, то логичнее применять as. Если же null не желателен, то кастуем. Для себя давно решил, что as у меня применяется только на верхнем уровне(например в controller mvc).
AgentFire
Очевидно, второй.
Какие-то детские задачки :S
Schvepsss
Мы параллельно запустили голосовалки в соц. сетях, в Telegram пока побеждает первый (65%).
mayorovp
Тем не менее, я почему-то очень часто вижу именно первый вариант в чужом коде...
qw1
У меня такие же наблюдения…
Программисты выучивают
FirstOrDefault()
и используют его везде, даже где более уместныFirst()
или ещё лучшеSingle()
.Используют
.Where(predicate).Count()
вместо.Count(predicate)
, или какие-то запутанные условия вOrderBy
вместоThenBy
, не используютCast()
иGroupBy()
AgentFire
Используют
.Where(predicate).Count()
вместо.Count(predicate)
где то я читал, что первый вариант оптимальней
mayorovp
Каким образом вариант, создающий лишнюю обертку, может быть оптимальнее?
qw1
Вероятно, дело в кривом поставщике данных, который не оптимизирует некоторые IQueryable.
Я легко могу представить linq-провайдера, который первый вариант компилирует в
select count(*) from t where ...
а второй — в
select count(case when ... then 1 else null end) from t