26

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;
}
thecoop
  • 45,220
  • 19
  • 132
  • 189

3 Answers3

38

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.

Ben
  • 3,255
  • 1
  • 26
  • 32
24

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.

casperOne
  • 73,706
  • 19
  • 184
  • 253
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 2
    @thecoop: had to change some other things, but I got it working. Damn this sure is a lot of trouble to get working! – R. Martinho Fernandes Feb 07 '11 at 18:04
  • 3
    @thecoop: I guess that's because it needs to support many different languages with different semantics. In VB, for instance, you can omit the return statement (with Option Strict Off), and that's why the default value is in there. I'm sure the other shenanigans are there support some other idiosyncracy of some other language. I sure hoped that at least the documentation was more forthcoming about all this. I had to do some trial & error and look through some DLR sources to figure it out. – R. Martinho Fernandes Feb 08 '11 at 13:04
  • 1
    See the other answers; it looks like this can be simplified, and it worked when I tried it. – Sam Sep 13 '13 at 12:02
  • Writing a bit of test code, I could get it to work by adding the `Expression.Constant` at the end of the `Expression.Block` and it worked. Not sure if you need labels etc. – Tahir Hassan Dec 28 '15 at 01:03
20

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.

Will Montgomery
  • 201
  • 1
  • 2