Примечание: Этот вопрос был задан до введения в .?
операторах в C # 6 / Visual Studio 2015 .
Мы все там были, у нас есть какое-то глубокое свойство, например cake.frosting.berries.loader, которое нам нужно проверить, является ли оно нулевым, чтобы не было исключений. Чтобы сделать это, используйте короткое замыкание оператора if.
if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...
Это не совсем изящно, и, возможно, должен быть более простой способ проверить всю цепочку и посмотреть, сталкивается ли она с нулевой переменной / свойством.
Возможно ли использование какого-либо метода расширения, или это будет языковая функция, или это просто плохая идея?
Ответы:
Мы рассмотрели возможность добавления новой операции «?». на язык, который имеет желаемую семантику. (И это было добавлено сейчас; см. Ниже.) То есть, вы бы сказали
и компилятор сгенерирует для вас все проверки на короткое замыкание.
Он не стал планкой для C # 4. Возможно, для гипотетической будущей версии языка.
Обновление (2014):
?.
оператор теперь планируется в следующей версии компилятора Рослин. Обратите внимание, что до сих пор ведутся споры о точном синтаксическом и семантическом анализе оператора.Обновление (июль 2015 г.): выпущена Visual Studio 2015, которая поставляется с компилятором C #, который поддерживает условные операторы NULL
?.
и?[]
.источник
Я был вдохновлен этим вопросом, чтобы попытаться выяснить, как такая глубокая проверка нуля может быть выполнена с помощью более простого / красивого синтаксиса с использованием деревьев выражений. Хотя я согласен с ответами, в которых говорится, что это может быть плохой дизайн, если вам часто требуется доступ к экземплярам в глубине иерархии, я также думаю, что в некоторых случаях, таких как представление данных, это может быть очень полезно.
Итак, я создал метод расширения, который позволит вам писать:
Это вернет Berries, если ни одна часть выражения не является нулевой. Если встречается значение null, возвращается значение null. Однако есть некоторые предостережения: в текущей версии он будет работать только с простым доступом к членам, и он работает только в .NET Framework 4, потому что он использует метод MemberExpression.Update, который является новым в v4. Это код для метода расширения IfNotNull:
Он работает, исследуя дерево выражений, представляющее ваше выражение, и оценивая части одну за другой; каждый раз проверяя, что результат не равен нулю.
Я уверен, что это можно расширить, чтобы поддерживать другие выражения, кроме MemberExpression. Считайте это кодом подтверждения концепции и имейте в виду, что его использование приведет к снижению производительности (что, вероятно, не имеет значения во многих случаях, но не используйте его в замкнутом цикле :-))
источник
DynamicInvoke
там. Я неукоснительно избегаю этого :)Я нашел это расширение весьма полезным для сценариев глубокого вложения.
Эту идею я заимствовал из оператора объединения NULL в C # и T-SQL. Приятно то, что возвращаемый тип всегда является возвращаемым типом внутреннего свойства.
Таким образом вы можете сделать это:
... или небольшое изменение вышеперечисленного:
Это не лучший синтаксис, который я знаю, но он работает.
источник
Помимо нарушения Закона Деметры, как уже указал Мехрдад Афшари, мне кажется, что для логики принятия решений вам нужна «глубокая проверка нуля».
Чаще всего это происходит, когда вы хотите заменить пустые объекты значениями по умолчанию. В этом случае вам следует рассмотреть возможность реализации шаблона нулевого объекта . Он действует как заменитель реального объекта, предоставляя значения по умолчанию и методы «бездействия».
источник
Обновление: начиная с Visual Studio 2015, компилятор C # (языковая версия 6) теперь распознает
?.
оператор, что упрощает «глубокую нулевую проверку». См. Этот ответ для подробностей.Помимо перепроектирования вашего кода, как предлагается в этом удаленном ответе , другим (хотя и ужасным) вариантом было бы использование
try…catch
блока, чтобы увидеть,NullReferenceException
происходит ли когда- нибудь во время этого глубокого поиска свойств.Я лично не стал бы этого делать по следующим причинам:
NullReferenceException
s, вероятно, никогда не следует перехватывать явно. (См. Этот вопрос .)Это почти наверняка должны быть функцию языка (которая доступна в C # 6 в форме
.?
и?[]
операторов), если C # уже не было более сложные ленивые вычисления, или если вы не хотите использовать отражение (который , вероятно , также не является хорошая идея по соображениям производительности и безопасности типов).Поскольку нет способа просто перейти
cake.frosting.berries.loader
к функции (она будет оценена и вызовет исключение с нулевой ссылкой), вам придется реализовать общий метод поиска следующим образом: он принимает объекты и имена свойств для уважать:(Примечание: код отредактирован.)
Вы быстро видите несколько проблем с таким подходом. Во-первых, вы не получаете никакой безопасности типов и возможной упаковки значений свойств простого типа. Во-вторых, вы можете либо вернуться,
null
если что-то пойдет не так, и вам нужно будет проверить это в вызывающей функции, либо вы выбросите исключение и вернетесь к тому месту, с которого начали. В-третьих, это может быть медленным. В-четвертых, это выглядит уродливее, чем то, с чего вы начали.Я бы либо остался с:
или воспользуйтесь приведенным выше ответом Мехрдада Афшари.
PS: Когда я писал этот ответ, я, очевидно, не рассматривал деревья выражений для лямбда-функций; см., например, ответ @driis для решения в этом направлении. Он также основан на некотором отражении и, следовательно, может работать не так хорошо, как более простое решение (
if (… != null & … != null) …
), но с точки зрения синтаксиса его можно считать лучше.источник
Хотя ответ дрииса интересен, я думаю, что это слишком дорого с точки зрения производительности. Вместо того, чтобы компилировать множество делегатов, я бы предпочел скомпилировать одну лямбду для каждого пути свойства, кэшировать ее, а затем повторно вызывать для многих типов.
NullCoalesce ниже делает именно это, он возвращает новое лямбда-выражение с нулевыми проверками и возврат значения по умолчанию (TResult) в случае, если какой-либо путь равен нулю.
Пример:
Вернет выражение
Код:
источник
Один из вариантов - использовать Null Object Patten, поэтому вместо null, когда у вас нет торта, у вас есть NullCake, который возвращает NullFosting и т. Д. Извините, я не очень хорошо объясняю это, но другие люди, см.
источник
Я тоже часто мечтал о более простом синтаксисе! Это становится особенно уродливым, когда у вас есть возвращаемые значения метода, которые могут быть нулевыми, потому что тогда вам понадобятся дополнительные переменные (например:
cake.frosting.flavors.FirstOrDefault().loader
)Однако вот довольно приличная альтернатива, которую я использую: создать вспомогательный метод Null-Safe-Chain. Я понимаю, что это очень похоже на ответ @ John выше (с
Coal
методом расширения), но я считаю, что это более просто и требует меньше ввода. Вот как это выглядит:Вот реализация:
Я также создал несколько перегрузок (от 2 до 6 параметров), а также перегрузок, которые позволяют цепочке заканчиваться типом значения или значением по умолчанию. У меня это действительно хорошо работает!
источник
Существует проект Maybe Codeplex, который реализует Maybe или IfNotNull с использованием лямбда-выражений для глубоких выражений в C #
Пример использования:
Ссылка была предложена в аналогичном вопросе Как проверить наличие нулей в глубоком лямбда-выражении?
источник
Как было предложено в John Leidegren «s ответа , один подход к работе вокруг этого является использование методов расширения и делегатов. Их использование может выглядеть примерно так:
Реализация запутана, потому что вам нужно заставить ее работать для типов значений, ссылочных типов и типов значений, допускающих значение NULL. Вы можете найти полную реализацию в Timwi «s ответа на Что такое правильный способ проверить для значений нуля? ,
источник
Или вы можете использовать отражение :)
Функция отражения:
Использование:
Мой случай (вернуть DBNull.Value вместо null в функции отражения):
источник
Попробуйте этот код:
источник
Я опубликовал это вчера вечером, а затем друг указал мне на этот вопрос. Надеюсь, поможет. Затем вы можете сделать что-то вроде этого:
Прочтите полную запись в блоге здесь .
Тот же друг также посоветовал вам посмотреть это .
источник
Expression
если вы собираетесь просто скомпилировать и поймать? Просто используйтеFunc<T>
.Я немного изменил код отсюда, чтобы он работал на заданный вопрос:
И да, это, вероятно, не оптимальное решение из-за проблем с производительностью try / catch, но оно работает:>
Использование:
источник
Если вам нужно этого добиться, сделайте следующее:
использование
или
Реализация вспомогательного класса
источник
Мне нравится подход Objective-C:
«Язык Objective-C использует другой подход к этой проблеме и не вызывает методы с nil, а вместо этого возвращает nil для всех таких вызовов».
источник