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

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

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

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

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

5 ответов

Решение

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

  • Арифметика: сложение, сложение, деление, по модулю, умножение, множитель, отрицание, отрицание, мощность, вычитание, вычитание, проверено, UnaryPlus
  • Создание: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
  • Побитовый: И, Эксклюзив или Левый сдвиг (<<), Нет, Или Правый сдвиг (>>)
  • Логический: 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, PostDecrementAssign, PostIncreignAssignSignSignSignSignSignSignSigned
  • Арифметика: Уменьшение, По умолчанию, Приращение, Единственное дополнение
  • Доступ пользователя: ArrayAccess, Динамический
  • Логический: ReferenceEqual, ReferenceNotEqual, TypeEqual
  • Поток: Блок, Разрыв, Продолжить, Пусто, Перейти, IfThen, IfThenElse, IfFalse, IfTrue, Метка, Цикл, Возврат, Переключатель, SwitchCase, Разблокировать, Переменная
  • Исключения: Поймать, Ретроу, Бросить
  • Отладка: ClearDebugInfo, DebugInfo

Выражение блока особенно интересно.

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

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

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

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

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

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

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

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

Редактировать:

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

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

Создайте метод do, т.е.

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

Другой способ взглянуть на эту проблему - помнить, что делегаты являются многоадресными; Вы можете объединить 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; }

}
Другие вопросы по тегам