Linq:IEnumerable上的扩展方法,用于在执行选择时自动执行空值检查

时间:2017-10-19 10:03:03

标签: c# linq c#-5.0

Select上执行IEnumerable时,我认为检查空引用是一种好习惯,因此我的Where之前经常会Select这样:

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

访问子属性时会变得更复杂:

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

要遵循这种模式,我需要对Where进行一次调用。我想在IEnumerable上创建一个扩展方法,根据Select中引用的内容自动执行此类空值检查。像这样:

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

可以这样做吗?是fx。在创建扩展方法时可以从selector参数中检索所选属性吗?

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

编辑:我在.NET Framework 4.5中使用C#5.0

4 个答案:

答案 0 :(得分:4)

当您使用C#5.0时,您可以通过以下方式编写扩展方法:

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

在投影(Select call)之前和之后应用检查结果不为空。 然后用法是:

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

请注意,每次通话中的项目类型都不同。

如果您确实希望它与此类似:

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

然后你需要使用@ Treziac的建议并使用?.运算符(在C#6.0中引入)然后过滤掉空值:

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

答案 1 :(得分:4)

您可以使用基于Expression的解决方案。以下是现场/财产链调用的基本且可行的解决方案。它适用于非常深的调用链。它并不完美。例如,如果链中有方法调用 obj.Prop1.MethodCall()。),将无效

基于表达式的解决方案通常较慢,因为需要将lambda表达式编译为委托,应考虑

效果统计

具有200k对象集合的睾丸,其嵌套调用级别为2(obj.Prop1.Prop2),其中所有对象都因条件而失败。

LINQ哪里有C#6?运营商: 2 - 4 ms

基于异常(try / catch): 14,000 - 15,000 ms

基于表达式: 4 - 10 ms

注意:基于表达式的解决方案会为每次调用增加几毫秒的开销,这个数字将不依赖于集合大小,因为表达式将针对每个调用进行编译操作。如果您有兴趣,可以考虑缓存机制。

基于表达式的解决方案的来源:

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

这是如何使用的:

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

答案 2 :(得分:2)

为什么不使用?.运算符?

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

(请注意,这可能会返回空值)

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

(这不会返回任何空值)

这不是真正好或坏的做法,取决于你想要的回报

答案 3 :(得分:2)

另一种选择是将选择空检查拆分为自定义运算符(例如WhereNotNull)。将其与?.运算符相结合,以非常有表现力的方式解决您的问题。

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

这允许你写:

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

如果不是,您可以随时链接selects(对于C#6之前的版本):

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

鉴于您绝对想要访问x.SomeProperty.SomeOtherProperty,最后一个选项是抓住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);
}