4

プロパティのいずれかが null になる可能性がある長いプロパティ チェーンの最後に値を抽出するコードがあります。

例えば:

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 が返されるようにプロパティ チェーンを処理するにはどうすればよいですか?

??のようなもの オペレーターは素晴らしいでしょう。

4

2 に答える 2

7

アップデート

C# 6 の時点で、 null 条件演算子を使用してソリューションが言語に組み込まれました。?.プロパティおよび?[n]インデクサー用。

null 条件演算子を使用すると、レシーバーが null でない場合にのみメンバーと要素にアクセスでき、それ以外の場合は null の結果が提供されます。

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

古い回答

私はそこにあるさまざまなソリューションを見てきました。それらのいくつかは、チェーンごとに追加されるノイズの量のためにあまり読みにくいため、私が好きではなかった複数の拡張メソッド呼び出しをチェーンすることを使用していました。

拡張メソッドの呼び出しが 1 つだけのソリューションを使用することにしました。これははるかに読みやすいためです。パフォーマンスについてはテストしていませんが、私の場合は、パフォーマンスよりも読みやすさが重要です。

このソリューションに大まかに基づいて、次のクラスを作成しました

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

xscholarshipApplication.Scholarship.CostScholarship.OfficialCurrentWorldRankingまたはnull、チェーン内のいずれかのプロパティが途中で null を返す場合の値が含まれます。

于 2010-11-25T23:32:28.357 に答える
2

これに対処するために、 IfNotNull 拡張メソッドを使用します。

それは世界で最も美しいものではありません(4層の深さになるのを見たら少し怖いでしょう)が、小さなケースではうまくいきます.

于 2010-11-22T05:36:18.397 に答える