如何将多个表达式组合成一个快速方法?

发布于 2024-08-24 10:28:20 字数 651 浏览 6 评论 0原文

假设我有以下表达式:

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 一起使用?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

↘人皮目录ツ 2024-08-31 10:28:20

不幸的是,在 .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、Invoke、Lambda、Parameter、TypeAs、Quote

。 NET 4 通过添加以下表达式来扩展此 API:

  • Mutation:AddAssign、AddAssignChecked、AndAssign、Assign、DivideAssign、ExclusiveOrAssign、LeftShiftAssign、ModuloAssign、MultiplyAssign、MultiplyAssignChecked、OrAssign、PostDecrementAssign、PostIncrementAssign、PowerAssign、PreDecrementAssign、PreIncrementAssign、RightShiftAssign、SubtractAssign、SubtractAssignChecked
  • 算术:递减、默认、递增、OnesComplement
  • 成员访问:ArrayAccess、动态
  • 逻辑:ReferenceEqual、ReferenceNotEqual、TypeEqual
  • 流程:Block、Break、Continue、Empty、Goto、IfThen、IfThenElse、IfFalse、IfTrue、Label、Loop、Return、Switch、 SwitchCase、取消装箱、变量
  • 异常:捕获、重新抛出、抛出
  • :ClearDebugInfo、DebugInfo

调试 .100).aspx" rel="nofollow noreferrer">Block 表达式特别有趣。

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.

情绪失控 2024-08-31 10:28:20

你可以,但这不是一个简单的任务。

当您有表达式类型的变量时,您可以检查其 Body 属性以查找表达式的数据结构。

您不能要求编译器为您编译它,因为它不会得到您想要的结果。您必须解析所有表达式的主体,并以某种方式将它们组合成一个方法,所有这些都是通过同时发出 IL (或者,如果您觉得 IL 太过分了,则通过生成 C# 并对其进行编译)。

正如 LINQ-to-SQL 将表达式编译为 SQL 查询一样,您也可以将表达式编译为您需要的任何内容。您将有很多工作要做,但您只需要实现您想要支持的内容即可。

在这个相当简单的例子中,我认为没有必要创建自己的 LINQ 提供程序。您可以只使用传递的表达式并从那里开始。但我怀疑你的申请比这更复杂一些。

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.

百思不得你姐 2024-08-31 10:28:20

在 4.0 中,由于支持树中的块操作(尽管在 C# 表达式编译器中不支持),这要容易得多。

但是,您可以通过利用 StringBuilder 公开“流畅”API 的事实来做到这一点;因此,您有一个 Func 而不是 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<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),但您必须做出一些决定关于限制复杂性。对于所呈现的代码,我可以在合理时间内完成它(仍然不简单),但如果您期望任何更复杂的表达式更适合您油煎。

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.

自控 2024-08-31 10:28:20

您只能在 .NET 4 中执行此操作。抱歉,我不知道详细信息。

编辑:

如果您对 Reflection.Emit 感到满意,您可以发出一个按顺序调用这些表达式的方法。

另一种选择:

创建一个“do”方法,即:

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

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();
}
独闯女儿国 2024-08-31 10:28:20

看待这个问题的另一种方法是记住委托是多播的;您可以多次组合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; }

}

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文