How can I combine several Expressions into a fast method?
Asked Answered
O

5

6

Suppose I have the following expressions:

Expression<Action<T, StringBuilder>> expr1 = (t, sb) => sb.Append(t.Name);
Expression<Action<T, StringBuilder>> expr2 = (t, sb) => sb.Append(", ");
Expression<Action<T, StringBuilder>> expr3 = (t, sb) => sb.Append(t.Description);

I'd like to be able to compile these into a method/delegate equivalent to the following:

void Method(T t, StringBuilder sb) 
{
    sb.Append(t.Name);
    sb.Append(", ");
    sb.Append(t.Description);
}

What is the best way to approach this? I'd like it to perform well, ideally with performance equivalent to the above method.

UPDATE So, whilst it appears that there is no way to do this directly in C#3 is there a way to convert an expression to IL so that I can use it with System.Reflection.Emit?

Ominous answered 10/3, 2010 at 17:24 Comment(1)
You want to be able to write something like: Expression<Action<T, StringBuilder>> combinedExperssion = expr1 + expr2 + expr3;?Ordination
B
4

Unfortunately in .NET 3.5 you cannot build an expression which performs a series of arbitrary operations. Here's the list of supported expressions:

  • Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, Subtract, SubtractChecked, UnaryPlus
  • Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
  • Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
  • Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, * LessThanOrEqual, NotEqual, OrElse (||), TypeIs
  • Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
  • Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote

.NET 4 extends this API by adding the following expressions:

  • Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked
  • Arithmetic: Decrement, Default, Increment, OnesComplement
  • Member Access: ArrayAccess, Dynamic
  • Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual
  • Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
  • Exceptions: Catch, Rethrow, Throw
  • Debug: ClearDebugInfo, DebugInfo

The Block expression is particularly interesting.

Bricole answered 14/3, 2010 at 13:37 Comment(2)
Mitsu Furuta from MS 'demoed' on a way to combine expressions using C#4 and a few tricks during French TechDays in Paris. Check his blog, and particularly this post : blogs.msdn.com/mitsu/archive/2010/03/02/…Masaryk
Here's another blog post you might find interesting: community.bartdesmet.net/blogs/bart/archive/2009/08/10/…Bricole
P
1

You can, but it's not trivial task.

When you have a variable of type Expression, you can inspect its Body property to find the data structure of the expression.

You can't ask the compiler to compile it for you because it will not get the result that you want. You'll have to parse the bodies of all your expressions and somehow combine them into a single method, all by emitting IL at the same time (or, by producing C# and have that compiled if you feel IL is a step too far).

Just as LINQ-to-SQL compiles the expression to a SQL query, so can you compile your expressions into whatever you need. You'll have a lot of work ahead of you, but you only need to implement that what you want to support.

In this rather trivial case I don't think it's necessary to create your own LINQ provider. You could just work with the expression as passed and go from there. But I suspect your application is a bit more complicated than that.

Pained answered 18/3, 2010 at 11:53 Comment(0)
O
1

In 4.0 this is much easier thanks to the support for block operations in the tree (although not in the C# expression compiler).

However, you could do this by exploiting the fact that StringBuilder exposes a "fluent" API; so instead of Action<T,StringBuilder> you have a Func<T,StringBuilder,StringBuilder> - like below (note that the actual syntax for expressing these expressions is identical in this case):

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }
    static void Foo<T>(T val) where T : IMyType
    {
        var expressions = new Expression<Func<T, StringBuilder, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        var tparam = Expression.Parameter(typeof(T), "t");
        var sbparam = Expression.Parameter(typeof(StringBuilder), "sb");

        Expression body = sbparam;
        for (int i = 0; i < expressions.Length; i++)
        {
            body = Expression.Invoke(expressions[i], tparam, body);
        }
        var func = Expression.Lambda<Func<T, StringBuilder, StringBuilder>>(
            body, tparam, sbparam).Compile();

        // now test it
        StringBuilder sbInst = new StringBuilder();
        func(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }
}

It is certainly possible to inspect the trees and emit IL manually (DynamicMethod perhaps), but you would have to make some decisions about limiting the complexity. For the code as presented I could do it in reasonable time (still not trivial), but if you expect anything more complex Expression is more your fried.

Onesided answered 20/3, 2010 at 8:43 Comment(0)
L
0

You can only do that in .NET 4. Sorry dont know the details.

Edit:

If you are comfortable with Reflection.Emit, you could emit a method calling those expressions in sequence.

Another alternative:

Create a 'do' method, ie:

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}
Leonardo answered 10/3, 2010 at 17:32 Comment(0)
O
0

Another way to look at this problem is to remember that delegates are multi-cast; you can combine an Action many times;

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }

    static void Foo<T>(T val) where T : IMyType {
        var expressions = new Expression<Action<T, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        Action<T, StringBuilder> result = null;
        foreach (var expr in expressions) result += expr.Compile();
        if (result == null) result = delegate { };
        // now test it
        StringBuilder sbInst = new StringBuilder();
        result(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }

}
Onesided answered 20/3, 2010 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.