Elegant way of reading a child property of an object

前端 未结 11 1193
半阙折子戏
半阙折子戏 2020-12-24 02:19

Say you are trying to read this property

var town = Staff.HomeAddress.Postcode.Town;

Somewhere along the chain a null could exist. What wo

相关标签:
11条回答
  • 2020-12-24 02:39

    You could also consider using the Maybe monad and having an extension method like ToMaybe() that gives you a Just a if the object is not null, a Nothing if it is.

    I won't go into the implementation details (unless someone asks) but the code would look like this:

    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);
    

    which is really clean or really ugly depending on you point of view

    0 讨论(0)
  • 2020-12-24 02:42

    I came up with the same solution as Ani's some time ago, see this blog post for details. While elegant, it's very inefficient...

    var town = Staff.NullSafeEval(s => s.HomeAddress.Postcode.Town, "(N/A)");
    

    A better solution IMHO is the one suggested in this CodeProject article:

    string town = Staff.With(s => s.HomeAddress)
                       .With(a => a.Postcode)
                       .With(p => p.Town);
    

    The only thing I don't like with this solution is the name of the extension method, but it can easily be changed...

    0 讨论(0)
  • 2020-12-24 02:43
        var town = "DefaultCity";
        if (Staff != null &&
            Staff.HomeAddress != null &&
            Staff.HomeAddress.Postcode != null &&
            Staff.HomeAddress.Postcode.Town != null)
        {
            town = Staff.HomeAddress.Postcode.Town;
        }
    
    0 讨论(0)
  • Another go:

    Declare a helper method

    bool HasNull(params object[] objects)
    {
        foreach (object o in objects) { if (o == null) return true; }
        return false;
    }
    

    Then use it like this:

    if (!HasNull(Staff, Staff.HomeAdress, Staff.HomeAddress.Postcode, Staff.HomeAddress.Postcode.Town))
    {
        town = Staff.HomeAddress.Postcode.Town;
    }
    
    0 讨论(0)
  • 2020-12-24 02:49

    I agree with Oded that this violates the Law of Demeter.

    I was intrigued by your question though, so I wrote up a a poor man's "Null-Safe Evaluate" extension-method with expression-trees, just for fun. This should give you compact syntax to express the desired semantics.

    Please don't use this in production code.

    Usage:

    var town = Staff.NullSafeEvaluate(s => s.HomeAddress.Postcode.Town);
    

    This will evaluate in succession:

    Staff
    Staff.HomeAddress
    Staff.HomeAddress.Postcode
    Staff.HomeAddress.Postcode.Town
    

    (Caching and reusing the values of the intermediate expressions to produce the next one)

    If it encounters a null reference, it returns the default value of the type of Town. Otherwise, it returns the value of the full expression.

    (Not throughly tested, can be improved in terms of performance and doesn't support instance-methods. POC only.)

    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;
    }
    
    0 讨论(0)
提交回复
热议问题