Delen via


C# 4 expressions: loops, goto, label, if and even for ! [Part III]

We now have block and variable support in our very small meta language.
Let’s try to use this basic engine to add higher functionalities.

.Net 4.0 expression API also brings new instructions such as Loop, Goto, Label, IfThenElse, etc.

We will add them with always the same process: adding a comprehensive method in the Block class and implement the appropriate transformation in the visitor.

First, here is the Block class with all our meta language dictionary. I have never written so many methods without implementing them ! :)

 public class Block
{
  //prevents the use of the default public
  //constructor
  //so we hare sure that the only way to get 
  //Block instances is to use Block.Start()
  private Block()
  {
  }
  
  public static Block Start<T>(T locals,
    Action<T> block)
  {
    throw new NotImplementedException();
  }
  
  public static R Start<T, R>(T locals,
    Func<T, R> block)
  {
    throw new NotImplementedException();
  }
  
  public static Block Default 
    { get; private set; }
  
  public Block _(Action action)
  {
    throw new NotImplementedException();
  }
  
  public Block Assign<T>(Func<T> leftExpression, 
    T rightExpression)
  {
    throw new NotImplementedException();
  }
  
  public Block Loop(Action block)
  {
    throw new NotImplementedException();
  }
  
  public Block IfThen(bool testExpression, 
    Action thenBlock)
  {
    throw new NotImplementedException();
  }
  
  public Block IfThenElse(bool testExpression, 
    Action thenBlock, Action elseBlock)
  {
    throw new NotImplementedException();
  }
  
  public Block While(bool testExpression,
    Action block)
  {
    throw new NotImplementedException();
  }
  
  public Block For(int from, Predicate<int> 
    condition, Action<int> block)
  {
    throw new NotImplementedException();
  }
  
  public Block Label(string name)
  {
    throw new NotImplementedException();
  }
  
  public Block Goto(string name)
  {
    throw new NotImplementedException();
  }
}

Then the code to recognize our methods and transform them to something understandable to the expression API :

 private Expression VisitBlockMethodCall(MethodCallExpression node)
{
  if (IsMethodOf<Block>(node, "Label"))
  {
    string labelName = 
      (string)(node.Arguments[0] as
        ConstantExpression).Value;
    return Expression.Label(
      GetLabelTarget(labelName));
  }
    
  if (IsMethodOf<Block>(node, "IfThen"))
    return 
      Expression.IfThen(
        Visit(node.Arguments[0]),
        Visit((node.Arguments[1] as 
          LambdaExpression).Body));
    
  if (IsMethodOf<Block>(node, "IfThenElse"))
    return 
      Expression.IfThenElse(
        Visit(node.Arguments[0]), 
        Visit((node.Arguments[1] as
          LambdaExpression).Body),
            Visit((node.Arguments[2] as 
        LambdaExpression).Body));

  if (IsMethodOf<Block>(node, "Goto"))
    {
      string labelName = 
        (string)(node.Arguments[0] as 
          ConstantExpression).Value;
      return Expression.Goto(
        GetLabelTarget(labelName));
    }

  if (IsMethodOf<Block>(node, "Loop"))
    {
      var result = (node.Arguments[0] as 
        LambdaExpression).Body;
      return Expression.Loop(Visit(result));
    }

  if (IsMethodOf<Block>(node, "While"))
    {
      var exitLabel = Expression.Label();

      var block =
        Expression.Block(
          new Expression[] {
            Expression.Loop(
              Expression.Block(
                new Expression[] {
                  Expression.IfThen(
                    Expression.Not(
                Visit(node.Arguments[0])),
                    Expression.Goto(exitLabel)),
                    Visit((node.Arguments[1] 
                as LambdaExpression).Body)
                })),
            Expression.Label(exitLabel)
          });
        return block;
    }
  if (IsMethodOf<Block>(node, "For"))
    {
      var l = node.Arguments[2]
        as LambdaExpression;
      var i = l.Parameters[0];
      var test = node.Arguments[1] 
        as LambdaExpression;
      var exitLabel = Expression.Label();
      
      var block =
        Expression.Block(
          new ParameterExpression[] { i },
          new Expression[] {
            Expression.Assign(i,
              Visit(node.Arguments[0])),
            Expression.Loop(
              Expression.Block(
                new Expression[] {
                  Expression.IfThen(
                    VisitWithReplaceParameter(
                      test.Parameters[0], i,
                      Expression.Not(test.Body)), 
                  Expression.Goto(exitLabel)),
                  VisitWithReplaceParameter(
                    l.Parameters[0], i, l.Body),
                  Expression.Assign(i,
                    Expression.Add(i,
                      Expression.Constant(1)))
                })),
              Expression.Label(exitLabel)
            });
      return block;
    }
  return null; 
}

You can notice that most of methods directly have a corresponding one in the expression API. But some of them are higher level ones like ‘While’ or ‘For’ that do not exist in the expression API.

So I had to implement a bigger transformation logic, using many functions of the expression API.

I can now write richer expressions mixing all those features:

 Expression<Action> expLabel = () =>
  Block.Default
    ._(() => Console.WriteLine("Start"))
    .Goto("exit")
    ._(() => Console.WriteLine("not printed"))
    .Label("exit")
    ._(() => Console.WriteLine("end"));

expLabel = ExpressionHelper.Translate(expLabel);
expLabel.Compile()();

Console.WriteLine("\nIfThenElse support");

Expression<Action> expIfThenElse = () =>
  Block.Start(new { s = "" }, b =>
    Block.Default
        ._(() => Console.Write("Enter 'OK'"))
        .Assign(() => b.s, Console.ReadLine())
        .IfThenElse(b.s == "OK",
            () => Console.WriteLine(
              "Good for me"),
            () => Console.WriteLine("Sorry"))
        );

expIfThenElse = ExpressionHelper.Translate(expIfThenElse);
expIfThenElse.Compile()();

Console.WriteLine("\nLoop support");

Expression<Action> expLoop = () =>
  Block.Start(new { i = 0 }, b =>
    Block.Default
      .Loop(() =>
        Block.Default
          ._(() => 
            Console.WriteLine("loop: " + b.i))
          .Assign(() => b.i, b.i + 1)
          .IfThen(b.i >= 4,
            () => Block.Default.Goto("break"))
          )
      .Label("break")
      ._(() => Console.WriteLine("end"))
  );

expLoop = ExpressionHelper.Translate(expLoop);
expLoop.Compile()();

Console.WriteLine("\nFor support");

Expression<Action> expFor = () =>
  Block.Default
    .For(0, i => i < 4,
      i => Console.WriteLine("For:" + i));

expFor = ExpressionHelper.Translate(expFor);
expFor.Compile()();

Console.WriteLine("\nWhile support");

Expression<Action> expWhile = () =>
  Block.Start(new { i = 0 }, b =>
    Block.Default
      .While(b.i < 4, 
        () => Block.Default
          ._(() => 
            Console.WriteLine("While:" + b.i))
          .Assign(() => b.i, b.i + 1))
      ._(() => Console.WriteLine("end"))
    );

expWhile = ExpressionHelper.Translate(expWhile);
expWhile.Compile()();

We now have richer C# expressions possibilities.

I hope one day we will have natural support for all of this and even more in our favorite language.

Mitsu

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

Comments

  • Anonymous
    March 03, 2010
    I've been reading this series with interest, and although I think it is really clever, I just can't see an application. Could you give me an example of somewhere you might use this technique? Thanks

  • Anonymous
    March 03, 2010
    Yes, I am currently working on some samples. First step, you can create your own transformation visitor and run it after mine and then compile the code with your own conditions/transformations. You can then optimize anything you want before compiling it. You can add log information dynamically when a method is called. But the biggest goal is metaprogramming. You can use the expression to generate something else than IL.

  • Sql ?
  • GPU instructions ?
  • Javascript ?
  • Anonymous
    March 29, 2010
    I'm with Darren - I've read the whole series, but I'm hoping you have some sort of metaprogramming example article to wrap this up that will help turn the light on for me. For example: "Using the mitsu metaprogramming system to generate strongly-typed dynamic SQL ORM classes for any domain object without reflection" or what have you. I really would like to see a concrete example of this in action that would answer the question "why", so that I don't write it off as another academic exercise. Or perhaps some other commenters could weigh in here and clear things up for us dullards...

  • Anonymous
    March 30, 2010
    I totally agree with Bruce. If example would give answer to question "why" I would be better off. Now I just see technical exercise without content.

  • Anonymous
    March 30, 2010
    Ok, ok, I will find some samples :) but I need time (and maybe skills) to do it. The story just changes a little bit. C# 3 expressions has already opened the door of meta programming. For example symbolic computation (see this example: http://www.elguille.info/NET/futuro/firmas_octavio_symbolic_computation_EN.htm ) where you can analyse a mathematical expression and work on it. It can be ordered, simplified, derived, displayed graphically, etc. Now we can start from a more complex expression but the goal is quite the same. Imagine a larger part of code that you could analyse to generate GPU instructions. Linq to Sql/Entities is also another example where you can generate a Sql query from a C# expression. Now we have statements, variable declaration and assign support maybe we could imagine transforming an expression into a stored procedure... We could also generate javascript code from C# to make it run inside a web browser. That's just some quick samples that comes to me but I am sure people could imagine a lot of other ones.

  • Anonymous
    May 13, 2010
    The comment has been removed

  • Anonymous
    April 01, 2014
    Love the posts Mitsu! Keep them coming.