Jus*_*gan 22 c# lambda expression-trees nullreferenceexception lifting
假设我有一个表达式,它只是一个成员访问运算符链:
Expression<Func<Tx, Tbaz>> e = x => x.foo.bar.baz;
Run Code Online (Sandbox Code Playgroud)
您可以将此表达式视为子表达式的组合,每个子表达式包含一个成员访问操作:
Expression<Func<Tx, Tfoo>> e1 = (Tx x) => x.foo;
Expression<Func<Tfoo, Tbar>> e2 = (Tfoo foo) => foo.bar;
Expression<Func<Tbar, Tbaz>> e3 = (Tbar bar) => bar.baz;
Run Code Online (Sandbox Code Playgroud)
我想要做的是分解e成这些组件子表达式,以便我可以单独使用它们.
如果我有表达x => x.foo.bar,我已经知道如何中断x => x.foo.如何拉出其他子表达式foo => foo.bar?
我试图在C#中模拟"提升"成员访问操作符,就像CoffeeScript的存在访问操作符一样?..Eric Lippert表示,类似的运营商被考虑用于C#,但没有预算来实施它.
如果这样的运算符存在于C#中,您可以执行以下操作:
value = target?.foo?.bar?.baz;
Run Code Online (Sandbox Code Playgroud)
如果target.foo.bar.baz链的任何部分结果为null,那么整个事情将评估为null,从而避免NullReferenceException.
我想要一个Lift可以模拟这种事情的扩展方法:
value = target.Lift(x => x.foo.bar.baz); //returns target.foo.bar.baz or null
Run Code Online (Sandbox Code Playgroud)
我有一些编译的东西,它有点工作.但是,它不完整,因为我只知道如何保留成员访问表达式的左侧.我可以x => x.foo.bar.baz变成x => x.foo.bar,但我不知道如何保持bar => bar.baz.
所以它最终会做这样的事情(伪代码):
return (x => x)(target) == null ? null
: (x => x.foo)(target) == null ? null
: (x => x.foo.bar)(target) == null ? null
: (x => x.foo.bar.baz)(target);
Run Code Online (Sandbox Code Playgroud)
这意味着表达式中最左边的步骤会一遍又一遍地进行评估.如果他们只是POCO对象的属性,但将它们变成方法调用并且效率低下(和潜在的副作用)变得更加明显,也许不是什么大不了的事:
//still pseudocode
return (x => x())(target) == null ? null
: (x => x().foo())(target) == null ? null
: (x => x().foo().bar())(target) == null ? null
: (x => x().foo().bar().baz())(target);
Run Code Online (Sandbox Code Playgroud)
static TResult Lift<T, TResult>(this T target, Expression<Func<T, TResult>> exp)
where TResult : class
{
//omitted: if target can be null && target == null, just return null
var memberExpression = exp.Body as MemberExpression;
if (memberExpression != null)
{
//if memberExpression is {x.foo.bar}, then innerExpression is {x.foo}
var innerExpression = memberExpression.Expression;
var innerLambda = Expression.Lambda<Func<T, object>>(
innerExpression,
exp.Parameters
);
if (target.Lift(innerLambda) == null)
{
return null;
}
else
{
////This is the part I'm stuck on. Possible pseudocode:
//var member = memberExpression.Member;
//return GetValueOfMember(target.Lift(innerLambda), member);
}
}
//For now, I'm stuck with this:
return exp.Compile()(target);
}
Run Code Online (Sandbox Code Playgroud)
value = x.ToMaybe()
.Bind(y => y.foo)
.Bind(f => f.bar)
.Bind(b => b.baz)
.Value;
Run Code Online (Sandbox Code Playgroud)
优点:
SelectMany并使用查询语法,恕我直言,看起来会更混乱,而不是更少.x.foo.bar.baz它的各个组件,这意味着我必须知道它们在编译时是什么.我不能只使用变量之类的表达式result = Lift(expr, obj);.我将Ian Griffith的LiftMemberAccessToNull方法修改为可以按照我所描述的方式使用的通用扩展方法.代码太长,不能包含在这里,但如果有人感兴趣,我会发布一个Gist.
优点:result = target.Lift(x => x.foo.bar.baz)语法Nullable<DateTime>成员工作.try
{
value = x.foo.bar.baz;
}
catch (NullReferenceException ex)
{
value = null;
}
Run Code Online (Sandbox Code Playgroud)
这是最明显的方式,如果我找不到更优雅的方式,那就是我将要使用的方式.
优点:我不会撒谎; "不承认失败"是我如此顽固的主要原因.我的直觉说必须有一种优雅的方式来做到这一点,但找到它一直是一个挑战.我无法相信它可以轻松访问表达式的左侧,但右侧几乎无法访问.
我这里真的有两个问题,所以我会接受任何可以解决其中任何一个的问题:
空传播成员访问被用于计划 包括在 C#6.0.不过,我仍然喜欢表达式分解的解决方案.
如果它只是一个简单的成员访问表达式链,那么有一个简单的解决方案:
public static TResult Lift<T, TResult>(this T target, Expression<Func<T, TResult>> exp)
where TResult : class
{
return (TResult) GetValueOfExpression(target, exp.Body);
}
private static object GetValueOfExpression<T>(T target, Expression exp)
{
if (exp.NodeType == ExpressionType.Parameter)
{
return target;
}
else if (exp.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression) exp;
var parentValue = GetValueOfExpression(target, memberExpression.Expression);
if (parentValue == null)
{
return null;
}
else
{
if (memberExpression.Member is PropertyInfo)
return ((PropertyInfo) memberExpression.Member).GetValue(parentValue, null);
else
return ((FieldInfo) memberExpression.Member).GetValue(parentValue);
}
}
else
{
throw new ArgumentException("The expression must contain only member access calls.", "exp");
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
如果要添加对方法调用的支持,请使用此更新方法:
private static object GetValueOfExpression<T>(T target, Expression exp)
{
if (exp == null)
{
return null;
}
else if (exp.NodeType == ExpressionType.Parameter)
{
return target;
}
else if (exp.NodeType == ExpressionType.Constant)
{
return ((ConstantExpression) exp).Value;
}
else if (exp.NodeType == ExpressionType.Lambda)
{
return exp;
}
else if (exp.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression) exp;
var parentValue = GetValueOfExpression(target, memberExpression.Expression);
if (parentValue == null)
{
return null;
}
else
{
if (memberExpression.Member is PropertyInfo)
return ((PropertyInfo) memberExpression.Member).GetValue(parentValue, null);
else
return ((FieldInfo) memberExpression.Member).GetValue(parentValue);
}
}
else if (exp.NodeType == ExpressionType.Call)
{
var methodCallExpression = (MethodCallExpression) exp;
var parentValue = GetValueOfExpression(target, methodCallExpression.Object);
if (parentValue == null && !methodCallExpression.Method.IsStatic)
{
return null;
}
else
{
var arguments = methodCallExpression.Arguments.Select(a => GetValueOfExpression(target, a)).ToArray();
// Required for comverting expression parameters to delegate calls
var parameters = methodCallExpression.Method.GetParameters();
for (int i = 0; i < parameters.Length; i++)
{
if (typeof(Delegate).IsAssignableFrom(parameters[i].ParameterType))
{
arguments[i] = ((LambdaExpression) arguments[i]).Compile();
}
}
if (arguments.Length > 0 && arguments[0] == null && methodCallExpression.Method.IsStatic &&
methodCallExpression.Method.IsDefined(typeof(ExtensionAttribute), false)) // extension method
{
return null;
}
else
{
return methodCallExpression.Method.Invoke(parentValue, arguments);
}
}
}
else
{
throw new ArgumentException(
string.Format("Expression type '{0}' is invalid for member invoking.", exp.NodeType));
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3650 次 |
| 最近记录: |