注意:在介绍the .?
operator in C# 6 / Visual Studio 2015之前询问了这个问题。
我们都去过那里,我们有像cake.frosting.berries.loader这样的深层属性,我们需要检查它是否为空,所以没有例外。要做的是使用短路if语句
if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...
这不是很优雅,也许应该有一种更简单的方法来检查整个链,看看它是否出现了null变量/属性。
是否可以使用某种扩展方法或者它是一种语言功能,还是只是一个坏主意?
答案 0 :(得分:218)
我们考虑过添加新操作“?”。到具有您想要的语义的语言。 (现在已添加;见下文。)也就是说,你会说
cake?.frosting?.berries?.loader
并且编译器会为您生成所有短路检查。
它没有成为C#4的标准。也许是对于该语言的假设未来版本。
更新(2014年):
?.
运算符现在是planned,用于下一个Roslyn编译器版本。请注意,对运算符的确切语法和语义分析仍存在争议。
更新(2015年7月): Visual Studio 2015已发布,附带支持null-conditional operators ?.
and ?[]
的C#编译器。
答案 1 :(得分:27)
我受到这个问题的启发,试图找出如何使用表达式树更简单/更漂亮的语法来完成这种深度的空值检查。虽然我同意答案,如果你经常需要访问层次结构深处的实例,那么可能是一个糟糕的设计,我也认为在某些情况下,例如数据表示,它可以非常有用。
所以我创建了一个扩展方法,允许你写:
var berries = cake.IfNotNull(c => c.Frosting.Berries);
如果表达式的任何部分都不为null,则返回Berries。如果遇到null,则返回null。但是有一些注意事项,在当前版本中它只能用于简单的成员访问,它只适用于.NET Framework 4,因为它使用了MemberExpression.Update方法,这是v4中的新方法。这是IfNotNull扩展方法的代码:
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;
}
}
}
它的工作原理是检查表达你的表达式的表达式树,并一个接一个地评估这些部分;每次检查结果都不为空。
我确信这可以扩展,以便支持除MemberOpression之外的其他表达式。将此视为概念验证代码,请记住,使用它会导致性能下降(在许多情况下可能无关紧要,但不要在紧密循环中使用它:-))< / p>
答案 2 :(得分:23)
我发现这个扩展对于深度嵌套场景非常有用。
public static R Coal<T, R>(this T obj, Func<T, R> f)
where T : class
{
return obj != null ? f(obj) : default(R);
}
这是我从C#和T-SQL中的空合并运算符中获得的想法。好处是返回类型始终是内部属性的返回类型。
这样你可以这样做:
var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);
...或上述的略有变化:
var berries = cake.Coal(x => x.frosting, x => x.berries);
这不是我所知道的最佳语法,但确实有效。
答案 3 :(得分:16)
除了违反德米特定律之外,正如Mehrdad Afshari已经指出的那样,在我看来,你需要对决策逻辑进行“深度空检查”。
当您想要使用默认值替换空对象时,通常会出现这种情况。在这种情况下,您应该考虑实施Null Object Pattern。它充当真实对象的替身,提供默认值和“非动作”方法。
答案 4 :(得分:10)
更新:从Visual Studio 2015开始,C#编译器(语言版本6)现在可以识别?.
运算符,这使得“深度空值检查”变得轻而易举。有关详细信息,请参阅this answer。
除了重新设计你的代码,比如
this deleted answer建议,
另一个(虽然可怕)选项是使用try…catch
块来查看在深度属性查找期间某个时间是否发生NullReferenceException
。
try
{
var x = cake.frosting.berries.loader;
...
}
catch (NullReferenceException ex)
{
// either one of cake, frosting, or berries was null
...
}
由于以下原因,我个人不会这样做:
NullReferenceException
应该永远不会被明确捕获。 (见this question。)因此可以使用某种扩展方法,或者它是一种语言功能,[...]
这几乎肯定必须是一种语言功能(在.?
和?[]
运算符的形式下以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" );
(注意:代码已编辑。)
您很快就会发现这种方法存在一些问题。首先,您不会获得任何类型安全性和可能的简单类型属性值的装箱。其次,如果出现问题,您可以返回null
,并且您必须在调用函数中检查这一点,或者抛出异常,然后返回到您开始的位置。第三,它可能很慢。第四,它看起来比你开始时更糟糕。
[...],还是只是一个坏主意?
我要么留下来:
if (cake != null && cake.frosting != null && ...) ...
或者使用Mehrdad Afshari的上述答案。
P.S。:当我写这个答案时,我显然没有考虑lambda函数的表达式树;见例如@driis'回答这个方向的解决方案。它也基于一种反射,因此可能不如简单的解决方案(if (… != null & … != null) …
)执行得更好,但从语法的角度来看它可能会更好。
答案 5 :(得分:5)
虽然driis的答案很有趣,但我认为这样做的性能太贵了。我不是编译许多委托,而是更喜欢为每个属性路径编译一个lambda,缓存它然后重新调用它多种类型。
下面的NullCoalesce就是这样,它返回一个带有空检查的新lambda表达式,如果任何路径为null,则返回默认值(TResult)。
示例:
NullCoalesce((Process p) => p.StartInfo.FileName)
将返回表达式
(Process p) => (p != null && p.StartInfo != null ? p.StartInfo.FileName : default(string));
代码:
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;
}
答案 6 :(得分:4)
一个选项是使用Null Object Patten,所以当你没有蛋糕时,不是没有null,你有一个NullCake返回NullFosting等。抱歉,我不是很擅长解释这个,但其他人是,见
答案 7 :(得分:3)
我也经常希望语法更简单!当你的方法返回值可能为null时会变得特别难看,因为那时你需要额外的变量(例如:cake.frosting.flavors.FirstOrDefault().loader
)
然而,这是我使用的一个相当不错的选择:创建一个Null-Safe-Chain辅助方法。我意识到这与@ John上面的答案非常类似(使用Coal
扩展方法),但我发现它更简单,更少打字。这是它的样子:
var loader = NullSafe.Chain(cake, c=>c.frosting, f=>f.berries, b=>b.loader);
以下是实施:
public static TResult Chain<TA,TB,TC,TResult>(TA a, Func<TA,TB> b, Func<TB,TC> c, Func<TC,TResult> r)
where TA:class where TB:class where TC:class {
if (a == null) return default(TResult);
var B = b(a);
if (B == null) return default(TResult);
var C = c(B);
if (C == null) return default(TResult);
return r(C);
}
我还创建了几个重载(有2到6个参数),以及允许链以值类型或默认值结束的重载。这对我来说真的很好用!
答案 8 :(得分:1)
试试这段代码:
/// <summary>
/// check deep property
/// </summary>
/// <param name="obj">instance</param>
/// <param name="property">deep property not include instance name example "A.B.C.D.E"</param>
/// <returns>if null return true else return false</returns>
public static bool IsNull(this object obj, string property)
{
if (string.IsNullOrEmpty(property) || string.IsNullOrEmpty(property.Trim())) throw new Exception("Parameter : property is empty");
if (obj != null)
{
string[] deep = property.Split('.');
object instance = obj;
Type objType = instance.GetType();
PropertyInfo propertyInfo;
foreach (string p in deep)
{
propertyInfo = objType.GetProperty(p);
if (propertyInfo == null) throw new Exception("No property : " + p);
instance = propertyInfo.GetValue(instance, null);
if (instance != null)
objType = instance.GetType();
else
return true;
}
return false;
}
else
return true;
}
答案 9 :(得分:1)
有Maybe codeplex project实施 也许或者IfNotNull在C#
中使用lambdas进行深层表达式使用示例:
int? CityId= employee.Maybe(e=>e.Person.Address.City);
类似问题was suggested
中的链接How to check for nulls in a deep lambda expression?答案 10 :(得分:1)
正如John Leidegren answer中所建议的,解决此问题的一种方法是使用扩展方法和委托。使用它们看起来像这样:
int? numberOfBerries = cake
.NullOr(c => c.Frosting)
.NullOr(f => f.Berries)
.NullOr(b => b.Count());
实现很混乱,因为您需要让它适用于值类型,引用类型和可空值类型。您可以在Timwi的answer到What is the proper way to check for null values?找到完整的实施。
答案 11 :(得分:1)
或者您可以使用反射:)
反射功能:
public Object GetPropValue(String name, Object obj)
{
foreach (String part in name.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
用法:
object test1 = GetPropValue("PropertyA.PropertyB.PropertyC",obj);
My Case(在反射函数中返回DBNull.Value而不是null):
cmd.Parameters.AddWithValue("CustomerContactEmail", GetPropValue("AccountingCustomerParty.Party.Contact.ElectronicMail.Value", eInvoiceType));
答案 12 :(得分:0)
我昨晚发布了这个,然后一位朋友向我指出了这个问题。希望能帮助到你。然后你可以这样做:
var color = Dis.OrDat<string>(() => cake.frosting.berries.color, "blue");
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
namespace DeepNullCoalescence
{
public static class Dis
{
public static T OrDat<T>(Expression<Func><T>> expr, T dat)
{
try
{
var func = expr.Compile();
var result = func.Invoke();
return result ?? dat; //now we can coalesce
}
catch (NullReferenceException)
{
return dat;
}
}
}
}
同一位朋友还建议你watch this。
答案 13 :(得分:0)
我稍微修改了here中的代码,使其适用于所提出的问题:
public static class GetValueOrDefaultExtension
{
public static TResult GetValueOrDefault<TSource, TResult>(this TSource source, Func<TSource, TResult> selector)
{
try { return selector(source); }
catch { return default(TResult); }
}
}
是的,由于尝试/捕获性能的影响,这可能不是最佳解决方案,但它可行:&gt;
用法:
var val = cake.GetValueOrDefault(x => x.frosting.berries.loader);
答案 14 :(得分:0)
您需要实现此目标,请执行以下操作:
用法
Color color = someOrder.ComplexGet(x => x.Customer.LastOrder.Product.Color);
或
Color color = Complex.Get(() => someOrder.Customer.LastOrder.Product.Color);
public static class Complex
{
public static T1 ComplexGet<T1, T2>(this T2 root, Func<T2, T1> func)
{
return Get(() => func(root));
}
public static T Get<T>(Func<T> func)
{
try
{
return func();
}
catch (Exception)
{
return default(T);
}
}
}
答案 15 :(得分:-3)
我喜欢Objective-C采取的方法:
“Objective-C语言采用了另一种解决此问题的方法,并没有在nil上调用方法,而是为所有这些调用返回nil。”
if (cake.frosting.berries != null)
{
var str = cake.frosting.berries...;
}