Home > Blockchain >  How do parameters and variables actually work in C# Expression trees?
How do parameters and variables actually work in C# Expression trees?

Time:09-01

I'm honestly just lost, I can't seem to make sense of how Expression tree syntax is supposed to function, and keep getting errors trying to create the simplest of lambdas.

Let's take a very basic function: you supply a number as a parameter, and it simply adds that to 0, yes it useless, but I can't even make that work:

public void AddNumber(int number)
    {
        var input = Expression.Parameter(typeof(int), "number");
        var result = Expression.Variable(typeof(int), "result");
        
        var addAssign = Expression.AddAssign(result, input);

        int myExpectedResult = Expression.Lambda<Func<int, int>>(addAssign, input).Compile()(number);
    }

I get an error because the variable result is created, but not defined, which makes sense. So I assume I need to assign some value to it to make it work:

public void AddNumber(int number)
    {
        var input = Expression.Parameter(typeof(int), "number");
        var result = Expression.Variable(typeof(int), "result");
        
        var addAssign = Expression.AddAssign(result, input);

        int myExpectedResult = Expression.Lambda<Func<int, int>>(addAssign, input).Compile()(number);
    }

Same error, I'm assuming because the assign isn't really taken into account as it is not used. But you also can't use that assign variable as a parameter for the AddAssign function, so it basically makes no difference.

Now, if I create a block expression to do both assignements in a row, this will get rid of the error, however, my "input" is now always set to 0:

        public void AddNumber(int number)
    {
        var input = Expression.Parameter(typeof(int), "number");
        var result = Expression.Variable(typeof(int), "result");

        var blockExpression = Expression.Block(new[] { input, result },
            Expression.Assign(result, Expression.Constant(0)),
            Expression.AddAssign(result, input));

        int myExpectedResult = Expression.Lambda<Func<int, int>>(blockExpression, input).Compile()(number);
    }

How are you actually supposed to simply add a parameter, create a variable, then assign a value to that variable to return it? I can't seem to find any tutorial that properly explains how to keep the link between parameters/variables, in different BlockExpressions.

Thanks a lot! Any help is appreciated!

CodePudding user response:

You're very close, but AddAssign is =, so you were trying to make something that looks like

x => x = y

where y is not defined and = can't write to x.

Instead try using Expression.Add for , and Expression.Constant for an integer value.

Something like the following should work:

var paramExp = Expression.Parameter(typeof(int), "x");
var intExp = Expression.Constant(5);

// x   5
var addExp = Expression.Add(paramExp, intExp);

// x => x   5
var lambdaExp = Expression.Lambda<Func<int, int>>(addExp, paramExp);

var result = lambdaExp.Compile()(3);

Assert.AreEqual(8, result);

For using Expression.Variable, I've never been able to get it to work either until now and thought it was impossible, but I now see that there are overloads of Expression.Block that take an array of variables that are to be defined in the block. Here is a similar example that uses a variable. Also note that the last expression in a block is what is returned from the block.

var paramExp = Expression.Parameter(typeof(int), "x");
var varExp = Expression.Variable(typeof(int), "y");
var valueExp = Expression.Constant(5);

// y = 5
var assignYExp = Expression.Assign(varExp, valueExp);

// x   y
var addExp = Expression.Add(paramExp, varExp);

// int y;
// y = 5;
// return x   y;
var blockExp = Expression.Block(
   new[] {
      varExp // variable declarations
   },
   assignYExp,
   addExp // return expression
);

// x => { ... }
var lambdaExp = Expression.Lambda<Func<int, int>>(blockExp, paramExp);

var result = lambdaExp.Compile()(3);

Assert.AreEqual(8, result);

CodePudding user response:

When writing expression trees, I always found it easier if I first wrote my result expression in a human readable form before trying to write it using an expression tree.

So the first step is - what are you trying to get? Is it number => number 0?

If that's the case, it is a lambda expression. What you want to do it start destructuring the result expression into smaller expression parts. In this case, we have the left side of the lambda expression which is number and the right side of the lambda expression which is number 0.

What do we see here? There is a parameter of type int called number, and there is a constant value of type int with value 0. What else is here - an Add expression.

So you first define your parameter:
var numberParamer = Expression.Parameter(typeof(int), "number");

Then you define your constant:
var constant = Expression.Constant(0);

Then define the add expression:
var addExpression = Expression.Add(constant, numberParamer);

And finally, the lambda:

var lambda = Expression.Lambda<Func<int, int>>(
  addExpression, // the first argument represents right side of the lambda
  numberParamer // the second argument takes a variable number of values, 
                // depending on the number of your parameters on the left side of your lambda. In this case, one
);

Now you can call it like:
var result = lambda.Compile()(number);

  • Related