Как я могу объединить несколько выражений в быстрый метод? - PullRequest
6 голосов
/ 10 марта 2010

Предположим, у меня есть следующие выражения:

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);

Я хотел бы иметь возможность скомпилировать их в метод / делегат, эквивалентный следующему:

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

Как лучше всего подойти к этому? Я хотел бы, чтобы он работал хорошо, в идеале с производительностью, эквивалентной описанному выше методу.

UPDATE Итак, хотя кажется, что в C # 3 нет способа сделать это напрямую, есть ли способ преобразовать выражение в IL, чтобы я мог использовать его с System.Reflection.Emit?

Ответы [ 5 ]

4 голосов
/ 14 марта 2010

К сожалению, в .NET 3.5 нельзя создать выражение, которое выполняет ряд произвольных операций. Вот список поддерживаемых выражений:

  • Арифметика: сложение, добавление, проверка, деление, по модулю, умножение, многократная проверка, отрицание, отрицательная проверка, мощность, вычитание, вычитание проверено, UnaryPlus
  • Создание: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
  • Побит: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
  • Логическое: AndAlso (&&), Условие (? :), Равно, GreaterThan, GreaterThanOrEqual, LessThan, * LessThanOrEqual, NotEqual, OrElse (||), TypeIs
  • членский доступ: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
  • Другое: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Параметр, TypeAs, Цитата

.NET 4 расширяет этот API, добавляя следующие выражения:

  • Мутация: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementSignign
  • Арифметика: Уменьшение, Значение по умолчанию, Приращение, Единственное дополнение
  • членский доступ: ArrayAccess, динамический
  • Логический: ReferenceEqual, ReferenceNotEqual, TypeEqual
  • Поток: Блок, Разрыв, Продолжить, Пусто, Перейти, IfThen, IfThenElse, IfFalse, IfTrue, Метка, Цикл, Возврат, Переключатель, SwitchCase, Unbox, Переменная
  • Исключения: Поймать, Ретроу, Бросить
  • Отладка: ClearDebugInfo, DebugInfo

Особенно интересно выражение Block .

1 голос
/ 20 марта 2010

В 4.0 это намного проще благодаря поддержке операций с блоками в дереве (хотя и не в компиляторе выражений C #).

Однако вы могли бы сделать это, используя тот факт, что StringBuilder предоставляет «свободный» API; поэтому вместо Action<T,StringBuilder> у вас есть Func<T,StringBuilder,StringBuilder> - как показано ниже (обратите внимание, что фактический синтаксис для выражения этих выражений идентичен в этом случае):

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; }
}

Конечно, возможно можно проверять деревья и излучать IL вручную (возможно, DynamicMethod), но вам придется принять некоторые решения об ограничении сложности. Для кода в представленном виде я мог бы сделать это за разумное время (все еще не тривиально), но если вы ожидаете, что что-то более сложное, Expression будет более жарким

1 голос
/ 18 марта 2010

Можно, но это не тривиальная задача.

Если у вас есть переменная типа Expression, вы можете проверить ее свойство Body, чтобы найти структуру данных выражения.

Вы не можете попросить компилятор скомпилировать его для вас, потому что он не получит желаемый результат. Вам нужно будет проанализировать тела всех ваших выражений и каким-то образом объединить их в единый метод, все путем одновременной генерации IL (или путем создания C # и его компиляции, если вы чувствуете, что IL слишком далеко).

Так же, как LINQ-to-SQL компилирует выражение в запрос SQL, так вы можете скомпилировать ваши выражения во все, что вам нужно. У вас впереди много работы, но вам нужно только реализовать то, что вы хотите поддерживать.

В этом довольно тривиальном случае я не думаю, что необходимо создавать своего собственного поставщика LINQ. Вы могли бы просто работать с выражением как пройдено и идти оттуда. Но я подозреваю, что ваше заявление немного сложнее, чем это.

0 голосов
/ 20 марта 2010

Еще один способ взглянуть на эту проблему - помнить, что делегаты являются многоадресными;Вы можете комбинировать Action много раз;

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; }

}
0 голосов
/ 10 марта 2010

Вы можете сделать это только в .NET 4. Извините, детали не известны.

Edit:

Если вам удобен Reflection.Emit, вы можете создать метод, вызывающий эти выражения в последовательности.

Другая альтернатива:

Создайте метод do, то есть:

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}
...