Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq: Extension method on IEnumerable to automatically do null-checks when performing selects

Tags:

c#

linq

c#-5.0

When performing a Select on an IEnumerable I believe it is good practice to check for null references, so I often have a Where before my Select like this:

someEnumerable.Where(x => x != null).Select(x => x.SomeProperty);

This gets more complicated when accessing sub-properties:

someEnumerable.Where(x => x != null && x.SomeProperty != null).Select(x => x.SomeProperty.SomeOtherProperty);

To follow this pattern I need to do a lof of calls to Where. I would like to create an extension method on IEnumerable that perform such null-checks automatically depending on what is referenced in the Select. Like this:

someEnumerable.SelectWithNullCheck(x => x.SomeProperty);
someEnumerable.SelectWithNullCheck(x => x.SomeProperty.SomeOtherProperty);

Can this be done? Is it fx. possible to retrieve the selected properties from the selector parameter when creating an extension method such as this?

public static IEnumerable<TResult> SelectWithNullCheck<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    return source.Where(THIS IS WHERE THE AUTOMATIC NULL-CHECKS HAPPEN).Select(selector);
}

EDIT: I use C# 5.0 with .NET Framework 4.5

like image 551
ebug Avatar asked Oct 19 '17 10:10

ebug


4 Answers

As you are using C# 5.0 you can write your extension method the following way:

public static IEnumerable<TResult> SelectWithNullCheck<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TResult> selector)
{
    return source.Where(x => x != null).Select(selector).Where(x => x != null);
}

Before and after the projection (Select call) to apply a check that the result is not null. Then usage will be:

someEnumerable.SelectWithNullCheck(x => x.SomeProperty)
              .SelectWithNullCheck(y => y.SomeOtherProperty);

Notice that the type of item in each call is different.


If you do want it similar to this:

someEnumerable.SelectWithNullCheck(x => x.SomeProperty.SomeOtherProperty);

Then you'd need to go with @Treziac's suggestion and use the ?. operator (introduced in C# 6.0) and then filter nulls out:

public static IEnumerable<TResult> SelectWithNullCheck<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    return source.Select(selector).Where( x=> x != null);
}

someEnumerable.SelectWithNullCheck(x => x?.SomeProperty?.SomeOtherProperty);
like image 81
Gilad Green Avatar answered Sep 29 '22 07:09

Gilad Green


You can use Expression based solution. Below is basic and workable solution for field / property chain call. It will work for very deep call chains. It is not perfect. For example it won't work if there is method call in the chain (obj.Prop1.MethodCall().Prop2).

Expression based solutions are in general slower, because of the need to compile the lambda expression to delegate, which should be taken in consideration.

Performance stats:

Testes with collection of 200k objects with nested call level of 2 (obj.Prop1.Prop2) where all objects fail for the condition.

LINQ Where with C# 6 ?. operator : 2 - 4 ms

Exception based (try / catch): 14,000 - 15,000 ms

Expression based: 4 - 10 ms

NOTE: The expression based solution will add overhead of several ms for every call, this number won't depend on the collection size, because the expression will be compiled for every call which is an expensive operation. You can think for cache mechanism if you are interested.

Source for expression based solution::

public static IEnumerable<T> IgnoreIfNull<T, TProp>(this IEnumerable<T> sequence, Expression<Func<T, TProp>> expression)
    {
        var predicate = BuildNotNullPredicate(expression);

        return sequence.Where(predicate);
    }

    private static Func<T, bool> BuildNotNullPredicate<T, TProp>(Expression<Func<T, TProp>> expression)
    {
        var root = expression.Body;
        if (root.NodeType == ExpressionType.Parameter)
        {
            return t => t != null;
        }

        var pAccessMembers = new List<Expression>();

        while (root.NodeType == ExpressionType.MemberAccess)
        {
            var mExpression = root as MemberExpression;

            pAccessMembers.Add(mExpression);

            root = mExpression.Expression;
        }

        pAccessMembers.Reverse();

        var body = pAccessMembers
            .Aggregate(
            (Expression)Expression.Constant(true),
            (f, s) =>
            {
                if (s.Type.IsValueType)
                {
                    return f;
                }

                return Expression.AndAlso(
                        left: f,
                        right: Expression.NotEqual(s, Expression.Constant(null))
                    );

            });

        var lambda = Expression.Lambda<Func<T, bool>>(body, expression.Parameters[0]);
        var func = lambda.Compile();

        return func;
    }

This is how is used:

var sequence = ....
var filtered = sequence.IgnoreIfNull(x => x.Prop1.Prop2.Prop3 ... etc);
like image 39
vasil oreshenski Avatar answered Sep 29 '22 06:09

vasil oreshenski


Why not using ?. operator ?

someEnumerable.Where(x => x?.SomeProperty != null).Select(x => x.SomeProperty.SomeOtherProperty);

(note that this may return null values)

or

someEnumerable.Select(x => x?.SomeProperty?.SomeOtherProperty).Where(x => x != null);

(this will not return any null values)

It's not really good or bad practice, it depends of what you want in your return

like image 39
Treziac Avatar answered Sep 29 '22 06:09

Treziac


Another option is to split the selection null check into a custom operator (e.g. WhereNotNull). Combine this with the ?. operator, solves your problem in an imho very expressive way.

public static IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource> source)
{
    return source.Where(x=> x != null);
}

This allows you to write:

someEnumerable.Select(x => x?.SomeProperty?.SomeOtherProperty)
              .WhereNotNull();

If not you could always chain the selects (for version prior to C# 6):

someEnumerable.Select(x => x.SomeProperty)
              .Select(x => x.SomeOtherProperty)
              .WhereNotNull();

Given you absolutely want to access x.SomeProperty.SomeOtherProperty the last option would be to catch the NullReferenceException.

public static IEnumerable<TResult> SelectWithNullCheck<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    return source.Select(x => 
                         {
                             try
                             {
                                 return selector(x);
                             }
                             catch(NullReferenceException ex)
                             {
                                 return default(TResult); 
                             }
                         })
                 .Where(x=> default(TResult) != x);
}
like image 43
Iqon Avatar answered Sep 29 '22 05:09

Iqon