C# 4 expressions: variables [Part II]


Reading the first part is mandatory !


The goal is still the same: enhance the C# 4 expression capabilities.


Now we have block support, let’s find a way to add variables. We can not really ‘code’ something without them…


In our first sample, the main lambda already had a parameter and we were able to use it inside our expression. Technically, adding variables is just creating new local parameters which is easy:


Expression.Parameter(typeof(int), “param”) then we can add this parameter as a variable of a block.


Once again, what is hard is to find a way to express and recognize them easily.


First idea, creating :


Block.DefineVariable(typeof(int), “param”)
Block.ReadVariable(“param”)
Block.AssignVariable(“param”, 1);


Easy to do and easy to transform, except it’s long to write and non typed. We would need a lot of cast instructions to make it run for real, heavy, no test at compile time, unsafe…not good.


Here is what I propose. As lambda can have access to the scope of their hosting method (see this article), we could imagine to create our needed variables outside the expression and then convert them to local parameter during the transformation.
Quite a strange solution with unnecessary declarations inside the hosting method but the idea is here. Let’s create an intermediate scope between the lambda and its hosting method.


public class Block
{
public static Block Start<T>(T locals, Action<T> block)
{
throw new NotImplementedException();
}
Imagine a Start method taking a first instance (that resolves the generic parameter T) and then a second argument defining an action with this a parameter of this same type.

Now imagine we use an anonymous type to define this first instance.



Expression<Action<int>> exp = i =>
Block.Start(
new { j = 1 }, b =>
Console.WriteLine(b.j)
);
In this code, type of b is defined by ‘new { j = 1 }’.
b is of course accessible in the body of the lambda and so are all its properties.
b.j is accessible and typed correctly.

The only default is b.j is read-only because such are anonymous types properties. But whatever, C# does not supports affectation in expressions. So we will symbolically write something like :



Block.Assign(() => b.j, 1);
…and we will transform it into something executable.

Let’s talk about the transformation. We need to recognize Block.Start, analyze the type of the first argument, create as many local parameters as the first argument exposes properties (same name, same type of course) and then replace all the references to b.X by the corresponding parameter. Last feature, we can initialize all the parameters with the values found in the anonymous type definition.
We also have to make it run correctly recursively.



private Expression VisitBlockMethodCall(MethodCallExpression node)
{
if (IsMethodOf<Block>(node, Assign))
{
var left
= node.Arguments[0]
as LambdaExpression;
return Expression.Assign(Visit(left.Body
,Visit(node.Arguments[
1]));
}
if (IsMethodOf<Block>(node, Start, true))
{
//Anonymous type analyzed to generate
//parameters
var newExpression = node.Arguments[0]
as NewExpression;
var variablesToAdd
=
newExpression.Type.GetProperties()
.Select((p, i)
=>
new {
Parameter
= Expression.Parameter(
p.PropertyType,
p.Name),
Value
= newExpression.Arguments[i]
}).ToArray();

//Prepares properties to be replaced
//by parameters and adds them to a
//global collection
var blockStartLambda = node.Arguments[1]
as LambdaExpression;
var parameters
=
variablesToAdd.ToDictionary(
v
=> v.Parameter.Name,
v
=> v.Parameter);

blockStartParameters.Add(
blockStartLambda.Parameters[0],
parameters);
//VisitMember will make the right
//replacements during this recursive
//call
var body = Visit(blockStartLambda.Body);
//Removes from collection
blockStartParameters.Remove(
blockStartLambda.Parameters[
0]);

//Creates a block with parameters
//declaration and initialization
var blockWithVariablesInitialization =
variablesToAdd.Select(v
=>
Expression.Assign(v.Parameter, v.Value))
.Concat(
new Expression[] { body });

var block = Expression.Block(
variablesToAdd.Select(v
=> v.Parameter),
blockWithVariablesInitialization);
return block;
}

The following code is using the prepared references of our anonymous type and replaces them in the VisitMember() method.


protected override Expression VisitMember(MemberExpression node)
{
if ((node.Member.MemberType
== MemberTypes.Property)
&& (node.Expression is ParameterExpression))
{
Dictionary
<string, ParameterExpression>
parameters
= null;
if (blockStartParameters.TryGetValue(
node.Expression
as ParameterExpression,
out parameters))
return parameters[node.Member.Name];
}
return base.VisitMember(node);
}
Now let’s see the result of this transformation.


Expression<Action<int>> exp = i =>
Block.Start(
new { j = 1 }, b =>
Console.WriteLine(b.j)
);
generates this expression :


.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $i) {
.Call CSharp4Expressions.Block.Start(
.New
<>f__AnonymousType0`1[System.Int32](1),
.Lambda #Lambda2
<System.Action`1[<>f__AnonymousType0`1[System.Int32]]>)
}

.Lambda #Lambda2<System.Action`1[<>f__AnonymousType0`1[System.Int32]]>(<>f__AnonymousType0`1[System.Int32] $b) {
.Call System.Console.WriteLine($b.j)
}

which is transformed into :


.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $i) {
.Block() {
.Block(System.Int32 $j) {
$j
= 1;
.Call System.Console.WriteLine($j)
}
}
}
We finally compile it and it runs correctly

image


Let’s test if we can nest declarations (variables local to child blocks).


Expression<Action<int>> exp = i =>
Block.Start(
new { j = 1 }, b =>
Block.Start(
new { i = 2 }, b2 =>
Console.WriteLine(b.j
+ b2.i)));


image


Ok we now have block support and a way to declare variables.
If reading is natural, we need to be able to assign values to those variables.


It’s a quite easy transformation. We first create an Assign() method in the Block class.



public class Block
{
public Block Assign<T>(Func<T> leftExpression, T rightExpression)
{
throw new NotImplementedException();
}
and then we catch it in our transformation engine replacing it with an Expression.Assign()


private Expression VisitBlockMethodCall(MethodCallExpression node)
{
if (IsMethodOf<Block>(node, Assign))
{
var left
= node.Arguments[0] as
LambdaExpression;
return Expression.Assign(
Visit(left.Body),
Visit(node.Arguments[
1]));
}
Let’s try it


Expression<Action<int>> exp = i =>
Block.Start(
new { j = 1 }, b =>
Block.Default
._(()
=> Console.WriteLine(b.j))
.Assign(()
=> b.j, 2)
._(()
=> Console.WriteLine(b.j))
);
and we correctly get

image


Enough for this post !


Next article will add Label, Goto, Loop, IfThenElse, While and For instructions.


The whole project is available here : http://code.msdn.microsoft.com/CSharp4Expressions/

Comments (2)

  1. Eduard Dumitru says:

    I have a question about the Assign method that you did.

    I have been trying to do something for a loong time and I think it’s either impossible, or .. I have become comfortably numb :)… (in the brain).

    I was hoping you had solved my problem.

    What I’m trying to do is to create methodOf, paramOf, fieldOf, localVariableOf, and so on, which can be thought of as being operators (just like typeof and default) who’s domain is a compile-time simbol and who’s result is a .. runtime object.

    Actually they’re all a compile-time trick and nothing actually happens at runtime.

    Well, I’m sure you already know the ideea of making some sort of class (in my case it’s called Of) which exposes some methods (in my case they’re called Of.Method, Of.Property, and so on) which define one parameter of type Expression<Func<T>> or whatever…

    and later on, one could say… I want to get the reflection object about my method.. that is called Foo, and hey, I can write this:

    MethodInfo mi = Of.Method(() => default(MyType).Foo(0, 0, false, false, DateTime.MinValue, null);

    and what I want to achieve by this.. is, have a means of reflecting on things already familiar to the reflective code, which only wishes to gain reflection entity references for these things but in a way that involves absolutely no String literals, which means that I can rest assure that whenever I change something in my code, either the Refactoring System will also change my lambdas or the compilers will point out the… change, and I’ll always be safe.

    Now. Here’s a thing I cannot even mention, in my ExpressionTree lambdas:

    A write-only property.

    Since it is write-only, you cannot return it using a Func<T> lambda. You also cannot use an Action lambda, since you need to do something with that property, and the only thing you could do would be to assign it a value.

    You most certainly cannot use the assignment operator.

    I was hoping you did this cause I saw you said .. Assign 🙂 in the first post :), but then I realised you are expecting fields, local variables, parameters or read-write properties, but you can’t treat write-only properties because you are actually .. pretending you are reading the property before actually .. writing to it, behind the scenes.

    Do you know if it’s safe to say that write-only properties cannot be "mentioned" in an Expression Tree ?

  2. mitsu says:

    Hi Eduard,

    Soory for the late response. As you can’t write any affectation in an expression tree, I think there’s no way to achieve what you are trying to do. You can build some tricks but you won’t have any kind of compile-time validation for your write-only property.