Can Roslyn be used to generate dynamic method similar to DynamicMethod IL generation
Asked Answered
B

3

9

I have been using DynamiMethod to generate the IL using

method.GetILGenerator();

This works well but is of course very hard to use since you generally don't want to work with low level IL in a high level language like C#. Now since there is Roslyn I though I can use that instead. I have tried to figure out how to use Roslyn to do similar thing: generate a dynamic method and then create a delegate for it. The only way I was able to do that is to have full class like this

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

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

Then instead of the Write method I can insert my method inside using string concatenation. After that dynamic assembly is generated in memory and loaded and reflection is used to get the required method and generate the delegate.

This method seems to work fine but seems a bit of an overkill for my case as I will need to use multiple independent methods possible leading to lots of assemblies being loaded.

So the question is: Is there an easy way to do something similar to dynamic method for Roslyn, so that I can only define a body of the method attached to a type? If not, is there any big drawback in compiling many dynamic assemblies (like too many can't be loaded, etc...)

Baffle answered 19/8, 2015 at 13:8 Comment(2)
Roslyn used to have a DynamicMethod emitter, but DynamicMethod had too many limitations, so it was removed.Tilth
That's a pity, as this should provide nice feature without messing with assembly loading...Baffle
M
2

You can use CSharpScript class. await CSharpScript.EvaluateAsync("1 + 2") just evaluates the expression. You can find it in Microsoft.CodeAnalysis.Scripting.CSharp package (currently only prerelease version). Add usings and assembly references using ScriptOptions (second parameter).

Compile expression to delegate:

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

Passing something to the function using globals object:

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

Monochord answered 6/11, 2015 at 20:6 Comment(2)
There is only one small issue. CSharpScript will for a moment increase your memory usage to around 1 GB but most of it will be cleanup after compilation.Lymphocytosis
@Lymphocytosis The memory usage increase is usually due to a high number of dependencies/references of your "Global" class. I guess the natural instinct is to just create a "Global" type as one more class in your project/dll (I did), but this will mean that the Roslyn compiler will have to load all of those dependencies in order to build your script. A neat trick is to host your "Global" type in a separate "Scripting" dll with the bare minimum external dependencies. Compilation time & memory will just... well just try it and see for yourself. A small detail hugely missing from the Roslyn doc.Czechoslovakia
M
2

I have one more idea how to solve your problem, that does not use Roslyn at all. You described it's annoying to emit IL using ILGenerator. However .NET Framework has build-in semantic trees, that can be compiled to dynamic methods. They live in Linq.Expression namespace and are also used in Linq providers.

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

You can do almost anything using it, it's much more comfortable than ILGenerator and is included in standard library.

Monochord answered 6/11, 2015 at 20:16 Comment(0)
Y
1

I'd like to comment on exyi's answer with Expression and Func<int,int>, but I don't have enough reputation. So here comes my "answer" instead.

If all you need is an first-class citizen piece of code that you can execute with parameters, you can simply create the Lambda like this:

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

If you need to have the actual expression tree, there is a neat shortcut as well:

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

The only difference in code is, that you wrapped the Func<int,int> with an Expression<..>. The conceptual difference is, that a Lambda (or Func<> in this example, but there are other Lambdas as well) can be executed as is, whereas an Expression<> needs to be compiled first with the Compile() method. But the Expression holds information about the syntax tree and can therefore be used for IQueryable data providers as used in Entity Framework.

So it all depends on what you want to do with your dynamic method/lamda/delegate.

Yann answered 11/7, 2018 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.