Linq для объектов, когда объект равен нулю VS Linq для SQL - PullRequest
6 голосов
/ 23 ноября 2011

У меня есть этот запрос Linq к объекту:

var result = Users.Where(u => u.Address.Country.Code == 12)

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

var result = Users.Where(u => u.Address != null &&
                              u.Address.Country != null &&  
                              u.Address.Country.Code == 12)

В Linq to SQL первый запрос выполнит работу (конечно, по другим причинам).

Есть ли способ избежать "нулевых проверок" в linq для объекта?

Ответы [ 2 ]

7 голосов
/ 23 ноября 2011

К сожалению, «ноль» трактуется непоследовательно в C # (и во многих других языках программирования).Nullable арифметика поднял .То есть, если вы выполняете арифметику с обнуляемыми целыми числами, и ни один из операндов не является нулевым, тогда вы получите нормальный ответ, но если какой-либо из них будет нулевым, вы получите нулевое.Но оператор "членского доступа" не отменен;если вы даете нулевой операнд члену доступа "."оператор, он выдает исключение, а не возвращает нулевое значение.

Если бы мы проектировали систему типов с нуля, мы могли бы сказать, что все типы обнуляются, а any *Выражение 1010 *, которое содержит нулевой операнд, дает нулевой результат независимо от типов операндов.Поэтому вызов метода с нулевым получателем или нулевым аргументом приведет к нулевому результату.Эта система имеет большой смысл, но, очевидно, нам уже слишком поздно внедрять ее;миллионы и миллионы строк кода были написаны, ожидая текущего поведения.

Мы рассмотрели добавление «поднятого» оператора доступа к элементу, возможно, записанного .?.Таким образом, вы могли бы тогда сказать where user.?Address.?Country.?Code == 12, и это привело бы к обнуляемому целому числу, которое затем можно было бы сравнить с 12, как это обычно бывает у обнуляемых чисел.Тем не менее, это никогда не проходило этап «да, это может быть хорошо в будущей версии» стадии процесса проектирования, поэтому я не ожидаю этого в ближайшее время.


ОБНОВЛЕНИЕ: «Элвис»упомянутый выше оператор был реализован в C # 6.0.

5 голосов
/ 23 ноября 2011

Нет, это исключение нулевой ссылки, точно так же, как доступ к var x = u.Address.Country.Code; будет NullReferenceException.

Вы всегда должны убедиться, что в LINQ к объектам вы не обращаетесь к null, как это было бы с любыми другими операторами кода.

Вы можете сделать это либо с помощью имеющейся у вас логики &&, либо с помощью цепочки предложений Where (хотя это будет содержать больше итераторов и, вероятно, работать медленнее):

var result = Users.Where(u => u.Address != null)
                  .Where(u.Address.Country != null)
                  .Where(u.Address.Country.Code == 12);

ПРИМЕЧАНИЕ : метод Maybe(), представленный ниже, просто предлагается как метод «Вы также можете», я не говорю, хорошо это или плохо, просто показываю, что делают некоторые люди. Пожалуйста, не голосуйте, если вам не нравится Maybe() Я просто повторяю различные решения, которые я видел ...

Я видел несколько написанных Maybe() методов расширения, которые позволяют вам делать то, что вы хотите. Некоторым людям это нравится / не нравится, потому что они являются методами расширения, работающими с ссылкой null. Я не говорю, что это хорошо или плохо, просто некоторые люди считают, что это нарушает хорошее ОО-подобное поведение.

Например, вы можете создать метод расширения, например:

public static class ObjectExtensions
{
    // returns default if LHS is null
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator)
        where TInput : class
    {
        return (value != null) ? evaluator(value) : default(TResult);
    }

    // returns specified value if LHS is null
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator, TResult failureValue)
        where TInput : class
    {
        return (value != null) ? evaluator(value) : failureValue;
    }
}

А затем сделайте:

var result = Users.Where(u => u.Maybe(x => x.Address)
                  .Maybe(x => x.Country)
                  .Maybe(x => x.Code) == 12);

По сути, это просто каскад null вниз по цепочке (или значение по умолчанию в случае не ссылочного типа).

UPDATE

Если вы хотите указать значение ошибки, отличное от значения по умолчанию (скажем, Код равен -1, если какая-либо часть имеет значение null), вы просто передадите новое значение ошибки в Maybe ():

// if you wanted to compare to zero, for example, but didn't want null
// to translate to zero, change the default in the final maybe to -1
var result = Users.Where(u => u.Maybe(x => x.Address)
                  .Maybe(x => x.Country)
                  .Maybe(x => x.Code, -1) == 0);

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

В настоящее время, однако, в C # не встроен нулевой безопасный оператор де-ссылки, поэтому вы либо используете условные проверки нуля, как вы делали это раньше, объединяйте свои операторы Where() в цепочку, чтобы они отфильтровывали null, или создайте что-нибудь, чтобы позволить вам каскадно null как Maybe() методы выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...