【问题标题】:Can Roslyn be used to generate dynamic method similar to DynamicMethod IL generationRoslyn可以用来生成类似于DynamicMethod IL生成的动态方法吗
【发布时间】:2015-11-12 19:22:45
【问题描述】:

我一直在使用 DynamiMethod 来生成 IL

method.GetILGenerator();

这很好用,但当然很难使用,因为您通常不希望在 C# 等高级语言中使用低级 IL。现在既然有罗斯林,我虽然可以用它来代替。我试图弄清楚如何使用 Roslyn 来做类似的事情:生成一个动态方法,然后为它创建一个委托。我能够做到这一点的唯一方法就是参加这样的完整课程

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;

namespace RoslynCompileSample
{
    public class Writer
    {
        public void Write(string message)
        {
            Console.WriteLine(message);
        }
    }
}");

然后,我可以使用字符串连接将我的方法插入其中,而不是 Write 方法。在内存中生成动态程序集并加载后,使用反射获取所需的方法并生成委托。

这种方法似乎工作正常,但对我的情况来说似乎有点过头了,因为我需要使用多种独立的方法,这可能会导致加载大量程序集。

所以问题是:是否有一种简单的方法可以为 Roslyn 执行类似于动态方法的操作,以便我只能定义附加到类型的方法体?如果没有,编译许多动态程序集是否有什么大的缺点(比如太多无法加载等...)

【问题讨论】:

  • Roslyn 曾经有一个DynamicMethod 发射器,但是DynamicMethod 限制太多,所以被移除了。
  • 很遗憾,因为这应该提供不错的功能,而不会弄乱程序集加载...

标签: c# .net compilation roslyn reflection.emit


【解决方案1】:

我想用ExpressionFunc<int,int> 评论exyi 的答案,但我没有足够的声誉。所以我的“答案”来了。

如果您只需要一段可以使用参数执行的一等公民代码,您可以像这样简单地创建 Lambda:

Func<int, int> add42 = number => number + 42;
// Called like this:
int theNumber46 = add42.Invoke(4);

如果您需要实际的表达式树,还有一个简洁的快捷方式:

Expression<Func<int, int>> add42 = number => number + 42;
// Called like this:
int theNumber46 = add42.Compile().Invoke(4);

代码的唯一区别是,您将Func&lt;int,int&gt; 包装为Expression&lt;..&gt;。概念上的区别在于,可以按原样执行 Lambda(或在此示例中为 Func&lt;&gt;,但还有其他 Lambda),而 Expression&lt;&gt; 需要首先使用 Compile() 方法编译。但Expression 包含有关语法树的信息,因此可用于实体框架中使用的IQueryable 数据提供程序。

所以这一切都取决于你想用你的动态方法/lamda/delegate做什么。

【讨论】:

    【解决方案2】:

    我还有一个想法如何解决您的问题,根本不使用 Roslyn。您描述了使用ILGenerator 发出 IL 很烦人。但是 .NET Framework 具有内置语义树,可以编译为动态方法。它们位于 Linq.Expression 命名空间中,也用于 Linq 提供程序。

    var parameter = Expression.Parameter(typeof(int), "a"); // define parameter
    var body = Expression.Add(parameter, Expression.Constant(42)); // sum parameter and number
    var lambdaExpression = Expression.Lambda<Func<int, int>>(new[] { parameter }, body); // define method
    var add42Delegate = lambdaExpression.Compile(); // compile to dynamic method
    

    您几乎可以使用它做任何事情,它比ILGenerator 舒适得多,并且包含在标准库中。

    【讨论】:

      【解决方案3】:

      您可以使用CSharpScript 类。 await CSharpScript.EvaluateAsync("1 + 2") 只计算表达式。你可以在Microsoft.CodeAnalysis.Scripting.CSharp 包中找到它(目前只有预发布版本)。使用 ScriptOptions(第二个参数)添加使用和程序集引用。

      编译表达式以委托:

      var func = CSharpScript.Create<int>("1 + 3").CompileToDelegate()
      

      使用全局对象向函数传递一些东西:

      await CSharpScript.Create<int>("1 + x", 
           ScriptOptions.Default.AddReferences(typeof(Program).Assembly),
           globalsType:  typeof(ScriptGlobals))
          .CreateDelegate()
          .Invoke(new ScriptGlobals() { x = 4 });
      

      【讨论】:

      • 只有一个小问题。 CSharpScript 会暂时将您的内存使用量增加到 1 GB 左右,但其中大部分将在编译后进行清理。
      • @mswietlicki 内存使用量增加通常是由于“全局”类的大量依赖项/引用。我想自然的直觉是在你的项目/dll中创建一个“全局”类型作为另一个类(我做了),但这意味着Roslyn编译器必须加载所有这些依赖项才能构建你的脚本.一个巧妙的技巧是将您的“全局”类型托管在一个单独的“脚本”dll 中,并具有最少的外部依赖项。编译时间和内存将......好吧,你自己试试看。 Roslyn 文档中严重缺失的一个小细节。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-17
      • 2014-08-06
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多