可以使用Roslyn生成类似于DynamicMethod IL生成的动态方法

时间:2015-08-19 13:08:23

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

我一直在使用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执行类似于动态方法的操作,这样我只能定义附加到类型的方法体?如果没有,编译许多动态程序集有什么大的缺点(比如太多无法加载等等)。

3 个答案:

答案 0 :(得分:2)

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

将表达式编译为委托:

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

使用globals对象将函数传递给函数:

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

答案 1 :(得分:1)

我还有一个想法如何解决你的问题,根本不使用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更舒服,并且包含在标准库中。

答案 2 :(得分:0)

我想用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<int,int>包裹了Expression<..>。概念上的区别在于,可以按原样执行Lambda(或本示例中的Func<>,但也有其他Lambda),而Expression<>首先需要与{{1 }} 方法。但是Compile()保留了有关语法树的信息,因此可以像EntityFramework中使用的那样供Expression数据提供者使用。

所以这一切都取决于您要对动态方法/ lamda /委托执行什么操作。