Linq to objects when object is null VS Linq to SQL

前端 未结 2 1269
孤独总比滥情好
孤独总比滥情好 2021-01-18 09:47

I have this Linq to object query:

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

I get an exception if the Address or the C

相关标签:
2条回答
  • 2021-01-18 10:16

    No, it is a null reference exception just like accessing var x = u.Address.Country.Code; would be a NullReferenceException.

    You must always make sure what you are de-referencing is not null in LINQ to objects, as you would with any other code statements.

    You can do this either using the && logic you have, or you could chain Where clauses as well (though this would contain more iterators and probably perform slower):

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

    NOTE: The Maybe() method below is just offered as an "you can also" method, I'm not saying it's good or bad, just showing what some people do. Please don't down-vote if you don't like the Maybe() I'm just repeating various solutions I've seen...

    I've seen some Maybe() extension methods written that let you do the sort of thing you want. Some people do/don't like these because they are extension methods that operate on a null reference. I'm not saying that's good or bad, just that some people feel that violates good OO-like behavior.

    For example, you could create an extension method like:

    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;
        }
    }
    

    And then do:

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

    Essentially, this just cascades the null down the chain (or default value in the case of a non-reference type).

    UPDATE:

    If you'd like to supply a non-default failure value (say Code is -1 if any part is null), you'd just pass the new failure value into 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);
    

    Like I said, this is just one of many solutions. Some people don't like being able to call extension methods from null reference types, but it is an option that some people tend to use to get around these null cascading issues.

    Currently, however, there is not a null-safe de-reference operator built into C#, so you either live with the conditional null checks like you had before, chain your Where() statements so that they will filter out the null, or build something to let you cascade the null like the Maybe() methods above.

    0 讨论(0)
  • 2021-01-18 10:28

    Unfortunately, "null" is treated inconsistently in C# (and in many other programming languages). Nullable arithmetic is lifted. That is, if you do arithmetic on nullable integers, and none of the operands are null, then you get the normal answer, but if any of them are null, you get null. But the "member access" operator is not lifted; if you give a null operand to the member access "." operator, it throws an exception rather than returning a null value.

    Were we designing a type system from scratch, we might say that all types are nullable, and that any expression that contains a null operand produces a null result regardless of the types of the operands. So calling a method with a null receiver or a null argument would produce a null result. That system makes a whole lot of sense, but obviously it is far too late for us to implement that now; millions upon millions of lines of code have been written that expects the current behaviour.

    We have considered adding a "lifted" member access operator, perhaps notated .?. So you could then say where user.?Address.?Country.?Code == 12 and that would produce a nullable int that could then be compared to 12 as nullable ints normally are. However, this has never gotten past the "yeah, that might be nice in a future version" stage of the design process so I would not expect it any time soon.


    UPDATE: The "Elvis" operator mentioned above was implemented in C# 6.0.

    0 讨论(0)
提交回复
热议问题