.NET 3.5/4.0、VS 2012/2013 中的空条件运算符模拟?

Null-conditional operator analog in .NET 3.5 / 4.0, VS 2012 / 2013?

在我的代码中,我必须访问调用多个 属性 getter 的值:

IFoo1 a = objA.Prop1.Value;
IFoo2 b = objB.Prop2.Prop3.Value;
IFoo3 c = objC.Prop4.Prop5.Prop6.Value;

每个 属性 都可以为空。所以要访问每个值,我必须使用嵌套的 if 块:

IFoo2 b = null;

if(objB.Prop2!=null)
{
    if(objB.Prop2.Prop3!=null)
    {
         b = objB.Prop2.Prop3.Value;
    }
}

如何改进此代码以减少 if 块的数量?我可以使用任何 lambda 表达式、LINQ、IExpression 等以某种方式将其替换为:

IFoo2 b = GetVal(objB.Prop2.Prop3.Value);

所有 PropX 都是不同类型的,我有数百个这样的属性。 我必须使用 .NET 3.5 或至少 .NET 4.0。我不能使用任何更高版本。

重要编辑:

我还必须使用 Visual Studio 2012 和 2013。我无法针对 VS 2015。

.NET 4.6 现在有一个 ?.解决这个确切问题的运算符。

http://www.volatileread.com/Wiki?id=2104

这允许你这样写。

IFoo3 c = objC?.Prop4?.Prop5?.Prop6?.Value;

您可以通过创建一个辅助函数来解决这个问题,但代码的清晰度和简单性将无法与此相提并论。如果可能,升级以获得该功能。

你能做的最好的事情就是使用像

这样的复合if条件
IFoo2 b = null;

if(objB.Prop2 != null && objB.Prop2.Prop3 != null)
{ 
  b = objB.Prop2.Prop3.Value;
}

(OR) 使用 Ternary Operator like

IFoo2 b = (objB.Prop2 != null && objB.Prop2.Prop3 != null) ? objB.Prop2.Prop3.Value : null;

这样就可以了,但是速度非常非常快而且很脏。一些明显的缺陷:

  1. 对于表达式 a.b.c - 我们调用 'a',然后是 'a.b',然后是 'a.b.c',每次都检查空值。我们应该存储上一次调用的 return ,并修改成员表达式以对我们的结果进行操作。仅当成员访问费用昂贵时才是真正的问题,否则它等同于 if (a != null && a.b != null && a.b.c != null) return a.b.c.d; 这是一种相当常见的模式
  2. 它只适用于成员表达式

public static T GetOrNull<T>(Expression<Func<T>> expression) 
    where T : class
{

    var memberExpressions = new List<MemberExpression>();
    var membExpress = expression.Body as MemberExpression;
    while (membExpress != null)
    {
        memberExpressions.Add(membExpress);
        membExpress = membExpress.Expression as MemberExpression;
    }
    memberExpressions.Skip(1).Reverse();

    foreach(var membExpr in memberExpressions.Skip(1).Reverse()) {
        var lambdaExpr = Expression.Lambda(membExpr);
        var currentRes = lambdaExpr.Compile().DynamicInvoke();
        if (currentRes == null)
            return null;
    }   

    return (T)Expression.Lambda(expression.Body).Compile().DynamicInvoke();
}

并像这样使用它:

var tmp = new classA();

var res = GetOrNull(() => tmp.Prop1.Prop2);
res.Dump(); //Gives null

tmp.Prop1 = new classA.classB();
tmp.Prop1.Prop2 = new classA.classB.classC();
res = GetOrNull(() => tmp.Prop1.Prop2);
res.Dump(); //returns object of type `classC`

很遗憾,您需要 C# 6 才能使用空条件运算符 (?.)。

但是,您可以使用如下扩展方法模拟它:

static class Extensions
{
    public static TReturn NCR<T, TReturn>(this T instance, Func<T, TReturn> getter)
        where T : class
        where TReturn : class
    {
        if (instance != null)
            return getter(instance);
        return null;
    }

    public static TReturn NCR<T, TReturn>(this T? instance, Func<T, TReturn> getter)
        where T : struct
        where TReturn : class
    {
        if (instance != null)
            return getter(instance.Value);
        return null;
    }

    public static TReturn? NCV<T, TReturn>(this T instance, Func<T, TReturn> getter)
        where T : class
        where TReturn : struct
    {
        if (instance != null)
            return getter(instance);
        return null;
    }

    public static TReturn? NCV<T, TReturn>(this T? instance, Func<T, TReturn> getter)
    where T : struct
    where TReturn : struct
    {
        if (instance != null)
            return getter(instance.Value);
        return null;
    }
}

(NC 代表 Null-Conditional,R 代表引用类型,V 代表值类型;它很丑,但不幸的是 C# 不允许仅在通用约束上有所不同的方法重载)

您可以这样使用它们:

IFoo3 c = objC.NCR(_ => _.Prop4)
              .NCR(_ => _.Prop5)
              .NCR(_ => _.Prop6)
              .NCR(_ => _.Value);

(如果 属性 获取 returns 值类型,则使用 NCV 而不是 NCR)

它仍然太冗长,但至少可以更容易地看到代码在做什么。