【问题标题】:Linq to objects when object is null VS Linq to SQL当对象为空时 Linq to objects VS Linq to SQL
【发布时间】:2011-11-22 21:49:44
【问题描述】:

我有这个 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 to object 中的“空检查”?

【问题讨论】:

    标签: c# .net linq linq-to-sql linq-to-entities


    【解决方案1】:

    不幸的是,“null”在 C#(以及许多其他编程语言)中的处理方式不一致。可空算术被提升。也就是说,如果你对可空整数进行算术运算,并且没有一个操作数为空,那么你会得到正常的答案,但如果其中任何一个为空,你就会得到空。但是“会员访问”操作符没有被解除了;如果你给成员访问“。”一个空操作数。运算符,它会抛出异常而不是返回空值。

    如果我们从头开始设计类型系统,我们可能会说所有类型都是可以为空的,并且包含空操作数的任何表达式都会产生空结果,而不管操作数的类型。所以调用带有空接收器或空参数的方法会产生空结果。这个系统很有意义,但显然我们现在实施它为时已晚;已经编写了数百万行预期当前行为的代码。

    我们已经考虑添加一个“提升的”成员访问运算符,可能标记为.?。所以你可以说where user.?Address.?Country.?Code == 12,这会产生一个可以为空的int,然后可以与12个可以为空的int进行比较。然而,这从未超过设计过程的“是的,这在未来的版本中可能会很好”阶段,所以我不会很快期待它。


    更新:上面提到的“Elvis”运算符是在 C# 6.0 中实现的。

    【讨论】:

    • 现在它是在语言中实现的(如果在语法上相反的话),但在 LINQ SQL 翻译中没有作为无操作实现,因此不容易使用。
    【解决方案2】:

    不,这是一个空引用异常,就像访问var x = u.Address.Country.Code; 将是一个NullReferenceException

    您必须始终确保您在 LINQ to 对象中取消引用的不是 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 引用上运行的扩展方法。我并不是说那是好是坏,只是有些人认为这违反了良好的类似 OO 的行为。

    例如,您可以创建如下扩展方法:

    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 向下级联(或在非引用类型的情况下为默认值)。

    更新

    如果你想提供一个非默认的失败值(假设代码为 -1,如果任何部分为空),你只需将新的失败值传递给 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() 语句,以便它们将过滤掉 @ 987654338@,或者构建一些东西让你级联null,就像上面的Maybe()方法一样。

    【讨论】:

    • 请注意,如果将Code0 进行比较,即使任何父属性为null,它也会返回true(因为0 是该类型的默认值)。跨度>
    • @GeorgeDuckett:是的,没错。这只是众多重载中的一个,实际上我在用于提供其他默认值的库中有几个重载。例如....(查看更新的代码)。
    • 我真的不喜欢那些可能的,当你阅读代码时更难理解,几乎和代码一样多。
    • @Magnus:正如我所说,我只是提供一个选项,有些人喜欢/不喜欢它们。我并不是说他应该/不应该使用它们,只是说这是一些开发人员编写的用于帮助处理空值的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-03
    • 2011-04-27
    • 1970-01-01
    • 2017-12-25
    相关资源
    最近更新 更多