假设您正在尝试阅读此属性
var town = Staff.HomeAddress.Postcode.Town;
沿链的某处可能存在null。 阅读城镇的最佳方式是什么?
我一直在试验几种扩展方法......
public static T2 IfNotNull<T1, T2>(this T1 t, Func<T1, T2> fn) where T1 : class
{
return t != null ? fn(t) : default(T2);
}
var town = staff.HomeAddress.IfNotNull(x => x.Postcode.IfNotNull(y=> y.Town));
或
public static T2 TryGet<T1, T2>(this T1 t, Func<T1, T2> fn) where T1 : class
{
if (t != null)
{
try
{
return fn(t);
}
catch{ }
}
return default(T2);
}
var town = staff.TryGet(x=> x.HomeAddress.Postcode.Town);
显然,这些只是抽象出逻辑并使代码(稍微)更具可读性。
但是有更好/更有效的方式吗?
编辑:
在我的特定情况下,对象是从WCF服务返回的,我无法控制这些对象的体系结构。
编辑2:
还有这种方法:
public static class Nullify
{
public static TR Get<TF, TR>(TF t, Func<TF, TR> f) where TF : class
{
return t != null ? f(t) : default(TR);
}
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
where T1 : class
where T2 : class
{
return Get(Get(p1, p2), p3);
}
/// <summary>
/// Simplifies null checking as for the pseudocode
/// var r = Pharmacy?.GuildMembership?.State?.Name
/// can be written as
/// var r = Nullify( Pharmacy, p => p.GuildMembership, g => g.State, s => s.Name );
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
where T1 : class
where T2 : class
where T3 : class
{
return Get(Get(Get(p1, p2), p3), p4);
}
}
来自这篇文章http://qualityofdata.com/2011/01/27/nullsafe-dereference-operator-in-c/
答案 0 :(得分:19)
最好的方法是避免违反law of Demeter。
var town = Staff.GetTown();
在Staff
:
string GetTown()
{
HomeAddress.GetTown();
}
在HomeAddress
:
string GetTown()
{
PostCode.GetTown();
}
在PostCode
:
string GetTown()
{
Town.GetTownName();
}
更新
由于您无法控制此问题,因此您可以使用short circuit evaluation:
if(Staff != null
&& Staff.HomeAddress != null
&& Staff.HomeAddress.PostCode != null
&& Staff.HomeAddress.PostCode.Town != null)
{
var town = Staff.HomeAddress.Postcode.Town;
}
答案 1 :(得分:10)
我同意Oded的观点,这违反了得墨忒耳法。
我对你的问题很感兴趣,所以我写了一个穷人的“无安全评估”#34;带表达式树的扩展方法,只是为了好玩。这应该为您提供紧凑的语法来表达所需的语义。
请不要在生产代码中使用它。
用法:
var town = Staff.NullSafeEvaluate(s => s.HomeAddress.Postcode.Town);
这将连续评估:
Staff
Staff.HomeAddress
Staff.HomeAddress.Postcode
Staff.HomeAddress.Postcode.Town
(缓存并重用中间表达式的值以生成下一个)
如果遇到null
引用,则返回Town
类型的默认值。否则,它返回完整表达式的值。
(未经过彻底测试,可以在性能方面进行改进,并且不支持实例方法。仅限POC。)
public static TOutput NullSafeEvaluate<TInput, TOutput>
(this TInput input, Expression<Func<TInput, TOutput>> selector)
{
if (selector == null)
throw new ArgumentNullException("selector");
if (input == null)
return default(TOutput);
return EvaluateIterativelyOrDefault<TOutput>
(input, GetSubExpressions(selector));
}
private static T EvaluateIterativelyOrDefault<T>
(object rootObject, IEnumerable<MemberExpression> expressions)
{
object currentObject = rootObject;
foreach (var sourceMemEx in expressions)
{
// Produce next "nested" member-expression.
// Reuse the value of the last expression rather than
// re-evaluating from scratch.
var currentEx = Expression.MakeMemberAccess
(Expression.Constant(currentObject), sourceMemEx.Member);
// Evaluate expression.
var method = Expression.Lambda(currentEx).Compile();
currentObject = method.DynamicInvoke();
// Expression evaluates to null, return default.
if (currentObject == null)
return default(T);
}
// All ok.
return (T)currentObject;
}
private static IEnumerable<MemberExpression> GetSubExpressions<TInput, TOutput>
(Expression<Func<TInput, TOutput>> selector)
{
var stack = new Stack<MemberExpression>();
var parameter = selector.Parameters.Single();
var currentSubEx = selector.Body;
// Iterate through the nested expressions, "reversing" their order.
// Stop when we reach the "root", which must be the sole parameter.
while (currentSubEx != parameter)
{
var memEx = currentSubEx as MemberExpression;
if (memEx != null)
{
// Valid member-expression, push.
stack.Push(memEx);
currentSubEx = memEx.Expression;
}
// It isn't a member-expression, it must be the parameter.
else if (currentSubEx != parameter)
{
// No, it isn't. Throw, don't support arbitrary expressions.
throw new ArgumentException
("Expression not of the expected form.", "selector");
}
}
return stack;
}
答案 2 :(得分:9)
var town = "DefaultCity";
if (Staff != null &&
Staff.HomeAddress != null &&
Staff.HomeAddress.Postcode != null &&
Staff.HomeAddress.Postcode.Town != null)
{
town = Staff.HomeAddress.Postcode.Town;
}
答案 3 :(得分:1)
根据封装,在返回它们之前,对它的字段(和属性)进行适当的验证(即空值检查)始终是一个类的义务。因此每个对象都负责其字段,您可以选择返回null,空字符串或引发异常并在链中处理它一级。试图解决这个问题就像尝试解决封装一样。
答案 4 :(得分:1)
这是一个使用空合并运算符的解决方案,我把它放在一起以获得乐趣(其他答案更好)。如果除了这个作为答案,我将不得不追捕你,呃,拿走你的键盘! : - )
基本上,如果Staff中的任何对象为null
,则会使用其默认值。
// define a defaultModel
var defaultModel = new { HomeAddress = new { PostCode = new { Town = "Default Town" } } };
// null coalesce through the chain setting defaults along the way.
var town = (((Staff ?? defaultModel)
.HomeAddress ?? defaultModel.HomeAddress)
.PostCode ?? defaultModel.HomeAddress.PostCode)
.Town ?? defaultModel.HomeAddress.PostCode.Town;
免责声明,我是一个javascript人,我们javascripters知道访问对象的属性可能会变得昂贵 - 所以我们倾向于缓存在所有内容之上,这就是上面的代码完成的内容(每个属性只是查找一旦)。使用C#的编译器和优化器,它可能没有必要这样做(对此有一些确认会很好)。
答案 5 :(得分:1)
前段时间我提出了与Ani's相同的解决方案,详见this blog post。优雅,但效率很低......
var town = Staff.NullSafeEval(s => s.HomeAddress.Postcode.Town, "(N/A)");
更好的解决方案恕我直言是this CodeProject article中建议的那个:
string town = Staff.With(s => s.HomeAddress)
.With(a => a.Postcode)
.With(p => p.Town);
我唯一不喜欢这个解决方案的是扩展方法的名称,但它很容易改变......
答案 6 :(得分:1)
@ Oded&#39;和其他人&#39;答案在2016年仍然适用,但是c#6引入了无条件运算符,它提供了你所追求的优雅。
using System;
public class Program
{
public class C {
public C ( string town ) {Town = town;}
public string Town { get; private set;}
}
public class B {
public B( C c ) {C = c; }
public C C {get; private set; }
}
public class A {
public A( B b ) {B = b; }
public B B {get; private set; }
}
public static void Main()
{
var a = new A(null);
Console.WriteLine( a?.B?.C?.Town ?? "Town is null.");
}
}
答案 7 :(得分:0)
您多久会看到一次null?如果(并且仅当)它很少,我会使用
try
{
var town = staff.HomeAddress.Postcode.Town;
// stuff to do if we could get the town
}
catch (NullReferenceException)
{
// stuff to do if there is a null along the way
}
答案 8 :(得分:0)
另一个去:
声明一个辅助方法
bool HasNull(params object[] objects)
{
foreach (object o in objects) { if (o == null) return true; }
return false;
}
然后像这样使用它:
if (!HasNull(Staff, Staff.HomeAdress, Staff.HomeAddress.Postcode, Staff.HomeAddress.Postcode.Town))
{
town = Staff.HomeAddress.Postcode.Town;
}
答案 9 :(得分:0)
您还可以考虑使用Maybe
monad并使用ToMaybe()
之类的扩展方法,如果对象不为空,则为Just a
,如果为Nothing
则为var maybeTown = from s in staff.ToMaybe()
from h in s.HomeAddress.ToMaybe()
from p in h.Postcode.ToMaybe()
from t in p.Town.ToMaybe()
select t;
var town = maybeTown.OrElse(null);
是
我不会深入讨论实现细节(除非有人问),但代码看起来像这样:
{{1}}根据你的观点,这是非常干净或非常丑陋
答案 10 :(得分:-1)
现在无法测试,但不会有这样的工作吗?
if (Staff??Staff.HomeAdress??Staff.HomeAddress.Postcode??Staff.HomeAddress.Postcode.Town != null)
{
var town = Staff.HomeAddress.Postcode.Town
}