注意:这个问题是在引进之前要求的.?在C#6/Visual Studio的2015年运营商.
我们都去过那里,我们有像cake.frosting.berries.loader这样的深层属性,我们需要检查它是否为空,所以没有例外.要做的是使用短路if语句
if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...
Run Code Online (Sandbox Code Playgroud)
这不是很优雅,也许应该有一种更简单的方法来检查整个链,看看它是否出现了null变量/属性.
是否可以使用某种扩展方法或者它是一种语言功能,还是只是一个坏主意?
Eri*_*ert 220
我们考虑过添加新的操作"?".到具有您想要的语义的语言.(现在已添加;见下文.)也就是说,你会说
cake?.frosting?.berries?.loader
Run Code Online (Sandbox Code Playgroud)
并且编译器会为您生成所有短路检查.
它没有成为C#4的标准.也许是对于该语言的假设未来版本.
更新(2014):
该?.运营商正在计划在未来罗斯林编译器版本.请注意,对运算符的确切语法和语义分析仍存在争议.
更新(2015年7月): Visual Studio 2015已经发布,并附带一个C#编译器,支持null条件运算符?.和?[].
dri*_*iis 27
I got inspired by this question to try and find out how this kind of deep null checking can be done with an easier/prettier syntax using expression trees. While I do agree with the answers stating that it might be a bad design if you often need to access instances deep in the hierarchy, I also do think that in some cases, such as data presentation, it can be very useful.
So I created an extension method, that will allow you to write:
var berries = cake.IfNotNull(c => c.Frosting.Berries);
Run Code Online (Sandbox Code Playgroud)
This will return the Berries if no part of the expression is null. If null is encountered, null is returned. There are some caveats though, in the current version it will only work with simple member access, and it only works on .NET Framework 4, because it uses the MemberExpression.Update method, which is new in v4. This is the code for the IfNotNull extension method:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace dr.IfNotNullOperator.PoC
{
public static class ObjectExtensions
{
public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
{
if (expression == null)
throw new ArgumentNullException("expression");
if (ReferenceEquals(arg, null))
return default(TResult);
var stack = new Stack<MemberExpression>();
var expr = expression.Body as MemberExpression;
while(expr != null)
{
stack.Push(expr);
expr = expr.Expression as MemberExpression;
}
if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
expression));
object a = arg;
while(stack.Count > 0)
{
expr = stack.Pop();
var p = expr.Expression as ParameterExpression;
if (p == null)
{
p = Expression.Parameter(a.GetType(), "x");
expr = expr.Update(p);
}
var lambda = Expression.Lambda(expr, p);
Delegate t = lambda.Compile();
a = t.DynamicInvoke(a);
if (ReferenceEquals(a, null))
return default(TResult);
}
return (TResult)a;
}
}
}
Run Code Online (Sandbox Code Playgroud)
It works by examining the expression tree representing your expression, and evaluating the parts one after the other; each time checking that the result is not null.
I am sure this could be extended so that other expressions than MemberExpression is supported. Consider this as proof-of-concept code, and please keep in mind that there will be a performance penalty by using it (which will probably not matter in many cases, but don't use it in a tight loop :-) )
Joh*_*ren 24
我发现这个扩展对于深度嵌套场景非常有用.
public static R Coal<T, R>(this T obj, Func<T, R> f)
where T : class
{
return obj != null ? f(obj) : default(R);
}
Run Code Online (Sandbox Code Playgroud)
这是我从C#和T-SQL中的空合并运算符中获得的一个想法.好处是返回类型始终是内部属性的返回类型.
这样你可以这样做:
var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);
Run Code Online (Sandbox Code Playgroud)
......或略有变化的上述内容:
var berries = cake.Coal(x => x.frosting, x => x.berries);
Run Code Online (Sandbox Code Playgroud)
这不是我所知道的最好的语法,但确实有效.
sta*_*ica 10
更新:从Visual Studio 2015开始,C#编译器(语言版本6)现在可以识别?.运算符,这使得"深度空值检查"变得轻而易举.有关详情,请参阅此答案.
除了重新设计你的代码,比如
这个删除的答案建议,另一个(尽管很可怕)选项是使用一个try…catch块来查看是否NullReferenceException在深度属性查找期间发生了某个时间.
try
{
var x = cake.frosting.berries.loader;
...
}
catch (NullReferenceException ex)
{
// either one of cake, frosting, or berries was null
...
}
Run Code Online (Sandbox Code Playgroud)
我个人不会这样做的原因如下:
NullReferenceException应该永远不应该明确地抓住s.(见这个问题.)所以可以使用一些扩展方法,或者它是一种语言功能,[...]
这几乎肯定必须是语言特性(在C#6中以.?和?[]运算符的形式提供),除非C#已经有更复杂的延迟评估,或者除非你想使用反射(可能也不是出于性能和类型安全的原因,这是个好主意.
由于无法简单地传递cake.frosting.berries.loader给函数(它将被计算并抛出空引用异常),因此您必须以下列方式实现常规查找方法:它将对象和属性的名称接受到抬头:
static object LookupProperty( object startingPoint, params string[] lookupChain )
{
// 1. if 'startingPoint' is null, return null, or throw an exception.
// 2. recursively look up one property/field after the other from 'lookupChain',
// using reflection.
// 3. if one lookup is not possible, return null, or throw an exception.
// 3. return the last property/field's value.
}
...
var x = LookupProperty( cake, "frosting", "berries", "loader" );
Run Code Online (Sandbox Code Playgroud)
(注意:代码已编辑.)
您很快就会发现这种方法存在一些问题.首先,您不会获得任何类型安全性和可能的简单类型属性值的装箱.其次,你可以在null出现问题时返回,你必须在你的调用函数中检查这个,或者你抛出异常,然后你就回到你开始的地方了.第三,它可能很慢.第四,它看起来比你开始时更丑陋.
[...],或者这只是一个坏主意?
我会留下来:
if (cake != null && cake.frosting != null && ...) ...
Run Code Online (Sandbox Code Playgroud)
或者选择Mehrdad Afshari的上述答案.
PS:当我写这个答案时,我显然没有考虑lambda函数的表达式树; 看看例如@driis'的答案,找到这个方向的解决方案.它也基于一种反射,因此可能不如简单的解决方案(if (… != null & … != null) …)表现得好,但是从语法的角度来看它可能会更好.
虽然driis的答案很有趣,但我认为这有点过于昂贵.我不是编译许多委托,而是更喜欢为每个属性路径编译一个lambda,缓存它然后重新调用它多种类型.
下面的NullCoalesce就是这样,它返回一个带有空检查的新lambda表达式,如果任何路径为null,则返回默认值(TResult).
例:
NullCoalesce((Process p) => p.StartInfo.FileName)
Run Code Online (Sandbox Code Playgroud)
将返回一个表达式
(Process p) => (p != null && p.StartInfo != null ? p.StartInfo.FileName : default(string));
Run Code Online (Sandbox Code Playgroud)
码:
static void Main(string[] args)
{
var converted = NullCoalesce((MethodInfo p) => p.DeclaringType.Assembly.Evidence.Locked);
var converted2 = NullCoalesce((string[] s) => s.Length);
}
private static Expression<Func<TSource, TResult>> NullCoalesce<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
{
var test = GetTest(lambdaExpression.Body);
if (test != null)
{
return Expression.Lambda<Func<TSource, TResult>>(
Expression.Condition(
test,
lambdaExpression.Body,
Expression.Default(
typeof(TResult)
)
),
lambdaExpression.Parameters
);
}
return lambdaExpression;
}
private static Expression GetTest(Expression expression)
{
Expression container;
switch (expression.NodeType)
{
case ExpressionType.ArrayLength:
container = ((UnaryExpression)expression).Operand;
break;
case ExpressionType.MemberAccess:
if ((container = ((MemberExpression)expression).Expression) == null)
{
return null;
}
break;
default:
return null;
}
var baseTest = GetTest(container);
if (!container.Type.IsValueType)
{
var containerNotNull = Expression.NotEqual(
container,
Expression.Default(
container.Type
)
);
return (baseTest == null ?
containerNotNull :
Expression.AndAlso(
baseTest,
containerNotNull
)
);
}
return baseTest;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
21492 次 |
| 最近记录: |