在表达式树中使用隐式转换运算符

Make use of implicit conversion operator inside expression tree

假设我有以下带有隐式转换运算符的类型:

public readonly struct WrappedInt
{
    public WrappedInt(int value)
    {
        Value = value;
    }

    public int Value { get; }

    public static implicit operator int (WrappedInt wrapper) => wrapper.Value;
}

那么这是一个应用程序:

var wrapper = new WrappedInt(42);
int x1 = Unwrap(wrapper);
int x2 = UnwrapUsingExpression(wrapper);

private static int Unwrap(WrappedInt wrapper)
{
    int result = wrapper;
    return result;
}

private static int UnwrapUsingExpression(WrappedInt wrapper)
{
    var wrapperParameterExpression = Expression.Parameter(typeof(WrappedInt), "wrapper");
    var resultVariableExpression = Expression.Variable(typeof(int), "result");

    Expression right = wrapperParameterExpression;     // THIS is important
    var assignExpression = Expression.Assign(resultVariableExpression, right);
    var blockExpression = Expression.Block(
        new ParameterExpression[] { resultVariableExpression },
        new Expression[] { resultVariableExpression, assignExpression, resultVariableExpression }
    );

    var unwrapFunc = Expression.Lambda<Func<WrappedInt, int>>(blockExpression, wrapperParameterExpression).Compile();
    return unwrapFunc(wrapper);
}

请注意,在 Unwrap 函数中我使用了隐式转换运算符 int result = wrapper; - 很好。

现在我想做同样的事情,但在表达式树中 - UnwrapWithExpression 例程。在这里,我使用

将 'result' 变量直接分配给 'wrapper' 参数

Expression right = wrapperParameterExpression;

但这不起作用 - 我收到运行时异常:

System.ArgumentException: 'Expression of type 'WrappedInt' cannot be used for assignment to type 'System.Int32'.

我知道如何解决它,基本上要么访问 Value 属性:

Expression right = Expression.Property(wrapperParameterExpression, nameof(WrappedInt.Value));

或转换它:

Expression right = Expression.Convert(wrapperParameterExpression, typeof(int));

但为什么我不能简单地使用原始的直接赋值并让我的隐式运算符完成工作?

表达式在很多方面比 C# 更受限制,尤其是在隐式类型转换方面。您会发现很多情况下,C# 编译器会为您插入类型转换,但您需要显式添加 Expression.Convert.

如果要调用隐式转换方法,需要加一个Expression.Convert。这就是表达式的工作方式,无论好坏。这样做不是解决方法。

如果你写表达式:

Expression<Func<WrappedInt, int>> convert = x => x;

然后在调试器中查看 convert.DebugView,您会看到:

.Lambda #Lambda1<System.Func`2[WrappedInt,System.Int32]>(WrappedInt $x) {
    (System.Int32)$x
}

(System.Int32)$x 是一个 Convert 节点(您可以通过在调试器中查看 convert.Body 来验证)。编译器决定在表达式领域与 C# 的最佳等价物是 Expression.Convert,所以这也是您应该做的。