How do I specify the object to return from an expr

2020-06-08 04:36发布

问题:

I'm trying to create a method using an expression tree that returns an object, but I can't figure out how to actually specify the object to return. I've tried reading this, but the return value doesn't actually seem to be specified anywhere.

I've got all the assignments & stuff down, but how do I specify the object to return from a method created using expression trees?

EDIT: these are v4 expression trees, and the method I'm trying to create does something like this:

private object ReadStruct(BinaryReader reader) {
    StructType obj = new StructType();
    obj.Field1 = reader.ReadSomething();
    obj.Field2 = reader.ReadSomething();
    //...more...
    return obj;
}

回答1:

Apparently a return is a GotoExpression that you can create with the Expression.Return factory method. You need to create a label at the end to jump to it. Something like this:

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

The types of the label target and the goto expression must match. Because the label target has a type, the label expression must have a default value.



回答2:

There is a much easier way to do this in cases that return an existing parameter or variable. The last statement in a Block Expression becomes the return value. You can include the ParameterExpression again at the end to have it returned.

Assuming your struct is like this:

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

Then your code would look like this:

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

Test that it works:

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

It's worth mentioning this example works if StructType is a struct. If it is a class, you just call the constructor and initialize structVar first thing in the BlockExpression.



回答3:

I have found a couple of sources that imply (one case) or actually state (another case) that returning a value from an expression can simply be done by concluding the block with the parameter expression that corresponds to the value in question, since the last valued expression from a block becomes its return value. Reportedly, the Expression.Return factory exists for more complicated cases where one returns from the middle of a block of code.

In other words, if the last expression inside your block were simply objExpression, that should be sufficient. I think, if you deconstruct all that business with the Return method and the label, that what actually happens is the objExpression is basically delivered to the label (at the end of the block) and left there, much as would occur if you eliminated returnExpression and returnLabel and simply concluded with objExpression.

Unfortunately, I am not in a position actually to test this myself.