如何在表达式树方法中指定要返回的对象?

26
我正在尝试使用表达式树创建一个返回对象的方法,但我无法确定如何指定要返回的对象。我已经阅读了这篇文章,但似乎没有实际指定返回值的地方。
我已经完成了所有的赋值和其他操作,但是如何从使用表达式树创建的方法中指定要返回的对象呢?
编辑:这些是 v4 表达式树,我正在尝试创建的方法类似于这样:
private object ReadStruct(BinaryReader reader) {
    StructType obj = new StructType();
    obj.Field1 = reader.ReadSomething();
    obj.Field2 = reader.ReadSomething();
    //...more...
    return obj;
}

由于这实际上有所不同,这些是v3还是v4表达式树?此外,您能展示一下您试图用树来表示的代码示例吗? - R. Martinho Fernandes
v4表达式树。编辑以添加代码。 - thecoop
3个回答

38

对于返回已有参数或变量的情况,有一种更简单的方法。块表达式中的最后一个语句将成为返回值。您可以在末尾再次包含ParameterExpression以使其返回。

假设您的结构体如下:

public struct StructType
{
    public byte Field1;
    public short Field2;
}

那么你的代码将会是这样:

var readerType = typeof(BinaryReader);
var structType = typeof(StructType);
var readerParam = Expression.Parameter(readerType);
var structVar = Expression.Variable(structType);

var expressions = new List<Expression>();

expressions.Add(
    Expression.Assign(
        Expression.MakeMemberAccess(structVar, structType.GetField("Field1")),
        Expression.Call(readerParam, readerType.GetMethod("ReadByte"))
        )
    );

expressions.Add(
    Expression.Assign(
        Expression.MakeMemberAccess(structVar, structType.GetField("Field2")),
        Expression.Call(readerParam, readerType.GetMethod("ReadInt16"))
        )
    );

expressions.Add(structVar); //This is the key. This will be the return value.

var ReadStruct = Expression.Lambda<Func<BinaryReader, StructType>>(
    Expression.Block(new[] {structVar}, expressions),
    readerParam).Compile();

测试它是否可行:

var stream = new MemoryStream(new byte[] {0x57, 0x46, 0x07});
var reader = new BinaryReader(stream);
var struct1 = ReadStruct(reader);

值得一提的是,如果StructType是一个结构体,那么这个示例将会起作用。如果它是一个类,你只需要调用构造函数并在块表达式中首先初始化structVar。


25

显然,return是一种可以使用Expression.Return工厂方法创建的GotoExpression。你需要在结尾处创建一个标签以跳转到它。像这样:

// an expression representing the obj variable
ParameterExpression objExpression = ...;

LabelTarget returnTarget = Expression.Label(typeof(StructType));

GotoExpression returnExpression = Expression.Return(returnTarget, 
    objExpression, typeof(StructType));

LabelExpression returnLabel = Expression.Label(returnTarget, defaultValue);

BlockExpression block = Expression.Block(
    /* ... variables, all the other lines and... */,
    returnExpression,
    returnLabel);
的目标类型和跳转表达式必须匹配。因为目标有一个类型,所以表达式必须具有默认值。

2
@thecoop:不得不改一些其他的东西,但我终于让它工作了。该死,这确实是为了让它工作而付出了很多麻烦! - R. Martinho Fernandes
3
@thecoop: 我想这是因为需要支持许多语言,并且这些语言具有不同的语义。例如,在VB中,您可以省略返回语句(使用Option Strict Off),这就是为什么默认值在其中的原因。我相信其他一些花式的方法是为了支持其他语言的某些特殊性。我真希望至少文档能更详细地说明这一点。我不得不进行一些试错,并查看一些DLR源代码才能弄清楚。 - R. Martinho Fernandes
1
看看其他答案,似乎可以简化,我尝试过它也起作用了。 - Sam
编写一些测试代码,我可以通过在Expression.Block的末尾添加Expression.Constant来使其工作。不确定是否需要标签等。 - Tahir Hassan

20

我发现了几个来源,它们暗示(一个案例)或者直接说明(另一个案例)从表达式返回值可以通过在区块结尾处添加对应于该值的参数表达式来简单完成,因为区块内最后一个有值的表达式就成为其返回值。据报道,Expression.Return 工厂针对那些需要在代码块中间返回的更复杂情况而存在。

换句话说,如果您区块内最后一个表达式仅仅是 objExpression,那么就足够了。我认为,如果你分解使用 Return 方法和 label 的全部流程,实际上发生的事情就是 objExpression 基本上被传递到标签(即区块结尾)并留在那里,就像如果你消除 returnExpression 和 returnLabel ,仅以 objExpression 结尾一样。

不幸的是,我没有机会亲自测试这个。


1
在.NET 4中对我有效。甚至只需在块表达式的末尾使用Expression.Constant(null)来返回null。 - Dave Cousineau
1
适用于.NET Core 2.1 - matiii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接