阅读对象的子属性的优雅方式

Dve*_*Dve 30 .net c# logic properties

假设您正在尝试阅读此属性

var town = Staff.HomeAddress.Postcode.Town;
Run Code Online (Sandbox Code Playgroud)

沿链的某处可能存在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));
Run Code Online (Sandbox Code Playgroud)

要么

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);
Run Code Online (Sandbox Code Playgroud)

显然,这些只是抽象出逻辑并使代码(一点点)更具可读性.

但是有更好/更有效的方式吗?

编辑:

在我的特定情况下,对象是从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);
    }
}
Run Code Online (Sandbox Code Playgroud)

来自这篇文章http://qualityofdata.com/2011/01/27/nullsafe-dereference-operator-in-c/

Ode*_*ded 19

最好的方法是避免违反得墨忒耳法律.

var town = Staff.GetTown();
Run Code Online (Sandbox Code Playgroud)

并在Staff:

string GetTown()
{
    HomeAddress.GetTown();
}
Run Code Online (Sandbox Code Playgroud)

并在HomeAddress:

string GetTown()
{
    PostCode.GetTown();
}
Run Code Online (Sandbox Code Playgroud)

并在PostCode:

string GetTown()
{
    Town.GetTownName();
}
Run Code Online (Sandbox Code Playgroud)

更新:

由于您无法控制此情况,因此可以使用短路评估:

if(Staff != null 
   && Staff.HomeAddress != null
   && Staff.HomeAddress.PostCode != null
   && Staff.HomeAddress.PostCode.Town != null)
{
    var town = Staff.HomeAddress.Postcode.Town;
}
Run Code Online (Sandbox Code Playgroud)


Ani*_*Ani 10

我同意Oded的说法,这违反了得墨忒耳法则.

我对你的问题很感兴趣,所以我写了一个穷人的"Null-Safe Evaluate"扩展方法和表达式树,只是为了好玩.这应该为您提供紧凑的语法来表达所需的语义.

请不要在生产代码中使用它.

用法:

var town = Staff.NullSafeEvaluate(s => s.HomeAddress.Postcode.Town);
Run Code Online (Sandbox Code Playgroud)

这将连续评估:

Staff
Staff.HomeAddress
Staff.HomeAddress.Postcode
Staff.HomeAddress.Postcode.Town
Run Code Online (Sandbox Code Playgroud)

(缓存并重用中间表达式的值以生成下一个)

如果遇到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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这很有意思! (3认同)

Evg*_*rin 9

    var town = "DefaultCity";
    if (Staff != null &&
        Staff.HomeAddress != null &&
        Staff.HomeAddress.Postcode != null &&
        Staff.HomeAddress.Postcode.Town != null)
    {
        town = Staff.HomeAddress.Postcode.Town;
    }
Run Code Online (Sandbox Code Playgroud)

  • 这可能不会让你在综合科学课堂上获得优雅/创造力的任何要点.但如果我"在野外"遇到这个代码,我会立即知道它在做什么以及为什么.有时简单/实用主义是最好的方法. (9认同)
  • @Dve - 它更糟糕,因为你依赖于抛出一个异常(如果它被抛出,它将会更加昂贵). (3认同)

Teo*_*gul 1

根据封装,类始终有责任在返回字段(和属性)之前对其字段(和属性)进行适当的验证(即空检查)。因此,每个对象都对其字段负责,您可以选择返回 null、空字符串,或者引发异常并在链中向上一级进行处理。尝试解决这个问题就像尝试解决封装一样。