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

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

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 так, чтобы я мог использовать его с Системой. Отражение. Испустить?

6
задан chillitom 18 March 2010 в 11:35
поделиться

5 ответов

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

  • Арифметика: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, Subtract, SubtractChecked, UnaryPlus
  • Создание: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
  • Побитовое: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
  • Логическое: AndAlso (&&), Condition (? :), Equal, GreaterThan , GreaterThanOrEqual, LessThan, * LessThanOrEqual, NotEqual, OrElse (||), TypeIs
  • Доступ к членам: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
  • Другое: Convert, ConvertChecked, Coalesce (??), Constant , Вызов, лямбда, параметр, TypeAs, цитата

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

  • Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PreDecrementAssign, PostDecrementAssign, SubtractAssign, SubtractAssignChecked
  • Арифметика: Decrement, Default, Increment, OnesComplement
  • Доступ к членам: ArrayAccess, Dynamic
  • Логический: ReferenceEqual, ReferenceNotEqual, TypeEqual
  • Поток: блок, разрыв, продолжение, пустой, Goto, IfThen , IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
  • Исключения: Catch, Rethrow, Throw
  • Debug: ClearDebugInfo, DebugInfo

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

4
ответ дан 17 December 2019 в 02:27
поделиться

Это можно сделать только в .NET 4. Извините, я не знаю подробностей.

Изменить:

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

Другой вариант:

Создайте метод «делать», например:

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}
0
ответ дан 17 December 2019 в 02:27
поделиться

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

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

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

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

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

1
ответ дан 17 December 2019 в 02:27
поделиться

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

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
ответ дан 17 December 2019 в 02:27
поделиться

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

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

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 ), но вам придется принять некоторые решения об ограничении сложность. Для кода , представленного , я мог бы сделать это за разумное время (все еще не тривиально), но если вы ожидаете чего-то более сложного, Выражение вам больше подходит.

1
ответ дан 17 December 2019 в 02:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: