Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq to objects when object is null VS Linq to SQL

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 Country are null.
Why this query doesn't check if the address is null and just after that procced? This way I won't need to write this terrible query:

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

In Linq to SQL the first query wiil do the job(from other reasons of course).

Is the a way to avoid the "null checks" in linq to object?

like image 841
gdoron is supporting Monica Avatar asked Nov 22 '11 21:11

gdoron is supporting Monica


2 Answers

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.

like image 112
Eric Lippert Avatar answered Nov 12 '22 13:11

Eric Lippert


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

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.

like image 44
James Michael Hare Avatar answered Nov 12 '22 14:11

James Michael Hare