gpt4 book ai didi

c# - 如何分解一系列成员访问表达式?

转载 作者:IT王子 更新时间:2023-10-29 04:31:36 26 4
gpt4 key购买 nike

简短版本(TL; DR):

假设我有一个表达式,该表达式只是一系列成员访问运算符:

Expression<Func<Tx, Tbaz>> e = x => x.foo.bar.baz;

您可以将此表达式视为子表达式的组成,每个子表达式都包含一个成员访问操作:

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;

我想要做的是将 e分解为这些组件子表达式,以便我可以单独使用它们。

更短的版本:

如果我有表达式 x => x.foo.bar,我已经知道如何断开 x => x.foo。如何提取其他子表达式 foo => foo.bar

我为什么要这样做:

我正在尝试模拟“提升” C#中的成员访问运算符,例如 CoffeeScript's existential access operator ?. 。埃里克·利珀特(Eric Lippert)表示使用 a similar operator was considered for C#,,但没有预算来实现它。

如果C#中存在这样的运算符,则可以执行以下操作:
value = target?.foo?.bar?.baz;

如果 target.foo.bar.baz链的任何部分结果为null,则整个结果将评估为null,从而避免了NullReferenceException。

我想要一种可以模拟这种情况的 Lift扩展方法:
value = target.Lift(x => x.foo.bar.baz); //returns target.foo.bar.baz or null

我尝试过的

我有一些可以编译的东西,并且可以正常工作。但是,它是不完整的,因为我只知道如何保留成员访问表达式的左侧。我可以将 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);

这意味着表达式中最左边的步骤会被一遍又一遍地求值。如果它们只是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);

代码:

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

这大致受 this answer启发。

提升方法的替代方法,以及为什么我不能使用它们:

The Maybe monad
value = x.ToMaybe()
.Bind(y => y.foo)
.Bind(f => f.bar)
.Bind(b => b.baz)
.Value;

优点:
  • 使用在函数式编程中流行的现有模式
  • Has other uses除了已取消成员访问权限

  • 缺点:
  • 太冗长了。我不想每次想钻几个成员时都使用大量的函数调用。即使我实现了SelectMany并使用了查询语法,恕我直言,它看起来也会更加困惑,而不是更少。
  • 我必须手动将x.foo.bar.baz重写为其单独的组件,这意味着我必须知道它们在编译时的含义。我不能只使用来自像result = Lift(expr, obj);这样的变量的表达式。
  • 并非真正针对我想做的事情而设计,并且感觉不完美。

  • ExpressionVisitor

    我将 Ian Griffith's LiftMemberAccessToNull method修改为可以如我所述使用的通用扩展方法。该代码太长,无法在此处包含,但是如果有人感兴趣,我会发布Gist。

    优点:
  • 遵循result = target.Lift(x => x.foo.bar.baz)语法
  • 如果链中的每个步骤都返回引用类型或非空值类型,则
  • 效果很好

    缺点:
  • 如果链中的任何成员都是可为空的值类型,这会很令人讨厌,这实际上限制了它对我的用处。我需要它才能用于Nullable<DateTime>成员。

  • 试着抓
    try 
    {
    value = x.foo.bar.baz;
    }
    catch (NullReferenceException ex)
    {
    value = null;
    }

    这是最明显的方法,如果找不到更优雅的方法,它将使用该方法。

    优点:
  • 很简单。
  • 很明显,代码的用途是什么。
  • 我不必担心边缘情况。

  • 缺点:
  • 丑陋而冗长的
  • try/catch块是nontrivial*性能命中的
  • 这是一个语句块,所以我不能使其发出LINQ的表达式树
  • 感觉就像失败了

  • 我不会撒谎; “不承认失败”是我如此固执的主要原因。我的直觉说必须要有一种优雅的方法来做到这一点,但是要找到它却是一个挑战。 我无法相信访问表达式的左侧是如此容易,但是右侧几乎是无法到达的。

    我这里确实有两个问题,所以我会接受任何一个可以解决任何一个问题的方法:
  • 保留双方的表达式分解,具有合理的性能,并且可以在任何
  • 类型上使用
  • 空传播成员访问


  • 更新:

    空传播成员访问 is planned for included in 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");
    }
    }

    编辑

    如果要添加对方法调用的支持,请使用以下更新的方法:
    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));
    }
    }

    关于c# - 如何分解一系列成员访问表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11108254/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com