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С первого взгляда, второй проще отладить, но если углубиться в задачу, то все равно второй вариант выигрывает:
FirstOrDefault
Alex_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