.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;
这样就可以了,但是速度非常非常快而且很脏。一些明显的缺陷:
- 对于表达式 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;
这是一种相当常见的模式
- 它只适用于成员表达式
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)
它仍然太冗长,但至少可以更容易地看到代码在做什么。
在我的代码中,我必须访问调用多个 属性 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;
这样就可以了,但是速度非常非常快而且很脏。一些明显的缺陷:
- 对于表达式 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;
这是一种相当常见的模式 - 它只适用于成员表达式
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)
它仍然太冗长,但至少可以更容易地看到代码在做什么。