Use DLR to run code generated with CompileAssembly

2019-02-22 19:12发布

Following up on this excellent answer, I'm wondering if the DLR using the dynamic keyword can allow a less verbose way of writing code for the generated assembly.

For example, can the aforementioned answer's code:

using (Microsoft.CSharp.CSharpCodeProvider foo = 
           new Microsoft.CSharp.CSharpCodeProvider())
{
    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters() {  
            GenerateInMemory = true 
        }, 
        "public class FooClass { public string Execute() { return \"output!\";}}"
    );

    var type = res.CompiledAssembly.GetType("FooClass");
    var obj = Activator.CreateInstance(type);
    var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
}

become something like:

using (Microsoft.CSharp.CSharpCodeProvider foo = 
           new Microsoft.CSharp.CSharpCodeProvider())
{
    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters() {  
            GenerateInMemory = true 
        }, 
        "public class FooClass { public string Execute() { return \"output!\";}}"
    );

    var type = res.CompiledAssembly.GetType("FooClass");
    dynamic obj = Activator.CreateDynamicInstance(type);
    var output = obj.Execute();
}

2条回答
不美不萌又怎样
2楼-- · 2019-02-22 19:30

Yes, you can do that and it works well. However, while using the dynamic keyword is more convenient, it utilizes late-binding and is still just as unsafe, in that sense, as explicitly using reflection. If your design allows it, it is even better to use a shared interface or base class for early-binding. You can do this by creating a public type in your assembly or in a third, shared assembly, and then add a reference to that assembly from the new one you are dynamically compiling. Then, in the generated code, you can inherit from that shared type in the referenced assembly. For instance, create an interface:

public interface IFoo
{
    string Execute();
}

Then dynamically compile the assembly like this:

using (Microsoft.CSharp.CSharpCodeProvider foo = new Microsoft.CSharp.CSharpCodeProvider())
{
    var params = new System.CodeDom.Compiler.CompilerParameters();
    params.GenerateInMemory = true;

    // Add the reference to the current assembly which defines IFoo
    params.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

    // Implement the IFoo interface in the dynamic code
    var res = foo.CompileAssemblyFromSource(params, "public class FooClass : IFoo { public string Execute() { return \"output!\";}}");
    var type = res.CompiledAssembly.GetType("FooClass");

    // Cast the created object to IFoo
    IFoo obj = (IFoo)Activator.CreateInstance(type);

    // Use the object through the IFoo interface
    obj.Execute();
}

Depending on how much control you have over the dynamic code, this may or may not be possible, but when it is, it's nice to have the compile-time type-checking. For instance, if you tried to execute:

IFoo obj = (IFoo)Activator.CreateInstance(type);
obj.Execcute();

That second line would immediately fail to compile because it's spelled wrong, whereas with the dynamic keyword or reflection, that line would successfully compile but it would cause a run-time exception. For instance, the following will not get a compile-time error:

dynamic obj = Activator.CreateDynamicInstance(type);
obj.Execcute();
查看更多
Bombasti
3楼-- · 2019-02-22 19:37

That is one of the scenarios that the DLR was designed for. You can use it that way to invoke members of a dynamically loaded type while avoiding all of the extra typing from manually calling .GetMethod() and .Invoke().

查看更多
登录 后发表回答