代码之家  ›  专栏  ›  技术社区  ›  John Mills

处理可能包含空值的属性链

  •  4
  • John Mills  · 技术社区  · 14 年前

    我有一些代码正在提取长属性链末尾的值,其中任何一个属性都可能为空。

    例如:

    var value = prop1.prop2.prop3.prop4;
    

    为了处理prop1中出现null的可能性,我必须编写:

    var value = prop1 == null ? null : prop1.prop2.prop3.prop4;
    

    为了处理prop1和prop2中出现null的可能性,我必须编写:

    var value = prop1 == null 
                ? null 
                : prop1.prop2 == null ? null : prop1.prop2.prop3.prop4;
    

    var value = prop1 != null && prop1.prop2 != null 
                ? prop1.prop2.prop3.prop4 
                : null;
    

    如果我想在prop1、prop2和prop3中处理null的可能性,或者甚至更长的属性链,那么代码开始变得非常疯狂。

    一定有更好的办法。

    如何处理属性链,以便在遇到空值时返回空值?

    像这样的??接线员很好。

    2 回复  |  直到 14 年前
        1
  •  7
  •   Community CDub    7 年前

    更新

    从C#6开始,一个解决方案现在被烘焙到语言中 null-conditional operator ; ?. 对于财产和 ?[n] 对于索引器。

    空条件运算符允许您访问成员和元素 仅当接收器不为空时,否则提供空结果:

    int? length = customers?.Length; // null if customers is null
    Customer first = customers?[0];  // null if customers is null
    

    旧答案

    我看了不同的解决方案。他们中的一些人使用链接多个扩展方法调用在一起,我不喜欢这样做,因为它不是很可读,因为为每个链添加了大量的噪声。

    我决定使用一个只涉及一个扩展方法调用的解决方案,因为它可读性更强。我没有测试性能,但在我的情况下,可读性比性能更重要。

    我创建了以下类,松散地基于 this solution

    public static class NullHandling
    {
        /// <summary>
        /// Returns the value specified by the expression or Null or the default value of the expression's type if any of the items in the expression
        /// return null. Use this method for handling long property chains where checking each intermdiate value for a null would be necessary.
        /// </summary>
        /// <typeparam name="TObject"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="instance"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static TResult GetValueOrDefault<TObject, TResult>(this TObject instance, Expression<Func<TObject, TResult>> expression) 
            where TObject : class
        {
            var result = GetValue(instance, expression.Body);
    
            return result == null ? default(TResult) : (TResult) result;
        }
    
        private static object GetValue(object value, Expression expression)
        {
            object result;
    
            if (value == null) return null;
    
            switch (expression.NodeType)
            {
                case ExpressionType.Parameter:
                    return value;
    
                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression)expression;
                    result = GetValue(value, memberExpression.Expression);
    
                    return result == null ? null : GetValue(result, memberExpression.Member);
    
                case ExpressionType.Call:
                    var methodCallExpression = (MethodCallExpression)expression;
    
                    if (!SupportsMethod(methodCallExpression))
                        throw new NotSupportedException(methodCallExpression.Method + " is not supported");
    
                    result = GetValue(value, methodCallExpression.Method.IsStatic
                                                 ? methodCallExpression.Arguments[0]
                                                 : methodCallExpression.Object);
                    return result == null
                               ? null
                               : GetValue(result, methodCallExpression.Method);
    
                case ExpressionType.Convert:
                    var unaryExpression = (UnaryExpression) expression;
    
                    return Convert(GetValue(value, unaryExpression.Operand), unaryExpression.Type);
    
                default:
                    throw new NotSupportedException("{0} not supported".FormatWith(expression.GetType()));
            }
        }
    
        private static object Convert(object value, Type type)
        {
            return Expression.Lambda(Expression.Convert(Expression.Constant(value), type)).Compile().DynamicInvoke();
        }
    
        private static object GetValue(object instance, MemberInfo memberInfo)
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)memberInfo).GetValue(instance);
                case MemberTypes.Method:
                    return GetValue(instance, (MethodBase)memberInfo);
                case MemberTypes.Property:
                    return GetValue(instance, (PropertyInfo)memberInfo);
                default:
                    throw new NotSupportedException("{0} not supported".FormatWith(memberInfo.MemberType));
            }
        }
    
        private static object GetValue(object instance, PropertyInfo propertyInfo)
        {
            return propertyInfo.GetGetMethod(true).Invoke(instance, null);
        }
    
        private static object GetValue(object instance, MethodBase method)
        {
            return method.IsStatic
                       ? method.Invoke(null, new[] { instance })
                       : method.Invoke(instance, null);
        }
    
        private static bool SupportsMethod(MethodCallExpression methodCallExpression)
        {
            return (methodCallExpression.Method.IsStatic && methodCallExpression.Arguments.Count == 1) || (methodCallExpression.Arguments.Count == 0);
        }
    }
    

    这允许我写下以下内容:

    var x = scholarshipApplication.GetValueOrDefault(sa => sa.Scholarship.CostScholarship.OfficialCurrentWorldRanking);
    

    x 将包含的值 scholarshipApplication.Scholarship.CostScholarship.OfficialCurrentWorldRanking null 如果链中的任何属性沿途返回空值。

        2
  •  2
  •   Community CDub    7 年前

    我用一个 IfNotNull extension method 来处理这个。

    这不是世界上最漂亮的东西(如果我看到它变深了4层,我会有点害怕),但它适用于较小的箱子。