Linq: extension method in IEnumerable to automatically perform null checks when performing selections

When doing Select on IEnumerable I find that good practice for null references is good practice, so I often have Where before my Select , like this:

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

This becomes 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 make lof Where calls. I would like to create an extension method on IEnumerable that automatically performs such null checks, depending on what the Select refers to. Like this:

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

Can this be done? This is fx. can I get 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 am using C # 5.0 with .NET Framework 4.5

+5
source share
4 answers

When you use C # 5.0, you can write your extension method as follows:

 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 projecting (Select call) apply a check so that the result is not zero. Then use will be:

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

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


If you really want this to look like:

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

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

 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); 
+4
source

You can use Expression solution. Below is a basic and functional solution for calling a circuit / network. It will work for very deep chains of calls. This is not perfect. For example, it does not work if there is a method call in the chain ( obj.Prop1.MethodCall (). Prop2 ).

Expression-based solutions are usually slower , due to the need to compile a lambda expression for delegation , which should be taken into account .

Performance Statistics :

Tests with a collection of 200k objects with a 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

Based on expression: 4 - 10 ms

NOTE An expression-based solution will add overhead of several ms for each call, this number will not depend on the size of the collection, as the expression will be compiled for each call, which is an expensive operation. You can think of a cache mechanism if you are interested.

Source for the solution based on the expression ::

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

Here's how to do it:

 var sequence = .... var filtered = sequence.IgnoreIfNull(x => x.Prop1.Prop2.Prop3 ... etc); 
+4
source

Why not use an 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)

This is not a good or bad practice, it depends on what you want in your return

+2
source

Another option is to split the null select check into a user statement (e.g. WhereNotNull ). Combine this with an operator ?. , solve your problem in imho in a 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 can always chain selects (for versions prior to C # 6):

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

Given that you absolutely want to access x.SomeProperty.SomeOtherProperty , the final option would be to catch a 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); } 
+2
source

Source: https://habr.com/ru/post/1272744/


All Articles