在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
答案 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);
}