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

发布于 2024-10-03 16:34:05 字数 770 浏览 0 评论 0原文

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

例如:

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,甚至更长的属性链,那么代码就会开始变得非常疯狂。

必须有更好的方法来做到这一点。

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

像 ?? 这样的东西运营商会很棒。

I have some code that is extracting a value at the end of a long property chain where any one of the properties could be null.

For example:

var value = prop1.prop2.prop3.prop4;

In order to handle the possibility of null in prop1 I have to write:

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

In order to handle the possibility of null in prop1 and prop2 I have to write:

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

or

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

If I want to handle the possibility of null in prop1, prop2 and prop3 as well, or even longer property chains, then the code starts getting pretty crazy.

There must be a better way to do this.

How can I handle property chains so that when a null is encountered, null is returned?

Something like the ?? operator would be great.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

阪姬 2024-10-10 16:34:05

更新

从 C# 6 开始,解决方案现已融入到该语言中 空条件运算符; ?. 用于属性,?[n] 用于索引器。

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

<前><代码>int?长度=客户?.长度; // 如果客户为空,则为空
客户至上 = 客户?[0]; // 如果客户为空,则为空


旧答案

我查看了那里的不同解决方案。其中一些使用将多个扩展方法调用链接在一起,我不喜欢这种做法,因为由于每个链添加的噪音量,它的可读性不太好。

我决定使用仅涉及单个扩展方法调用的解决方案,因为它更具可读性。我还没有测试性能,但就我而言,可读性比性能更重要。

我创建了以下类,松散地基于 此解决方案

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 如果链中的任何属性一路返回 null。

Update

As of C# 6, a solution is now baked into the language with the null-conditional operator; ?. for properties and ?[n] for indexers.

The null-conditional operator lets you access members and elements
only when the receiver is not-null, providing a null result otherwise:

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

Old Answer

I had a look at the different solutions out there. Some of them used chaining multiple extension method calls together which I didn't like because it wasn't very readable due to the amount of noise added for each chain.

I decided to use a solution that involved just a single extension method call because it is much more readable. I haven't tested for performance, but in my case readability is more important than performance.

I created the following class, based loosely on 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);
    }
}

This allows me to write the following:

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

x will contain the value of scholarshipApplication.Scholarship.CostScholarship.OfficialCurrentWorldRanking or null if any of the properties in the chains return null along the way.

醉梦枕江山 2024-10-10 16:34:05

我使用 IfNotNull 扩展方法 来处理这个问题。

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

I use an IfNotNull extension method to deal with this.

It's not the prettiest thing in the world (I'd be a bit scared if I saw it going 4 layers deep) but it works for the smaller cases.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文