Recompile C# while running, without AppDomains

给你一囗甜甜゛ 提交于 2019-11-29 21:01:52

Check out the namespaces around Microsoft.CSharp.CSharpCodeProvider and System.CodeDom.Compiler.

Compile the collection of .cs files

Should be pretty straightforward like http://support.microsoft.com/kb/304655

Will it matter that I am loading classes with the same name into the same process?

Not at all. It's just names.

instance the class that is inherited from LevelController.

Load the assembly that you created something like Assembly.Load etc. Query the type you want to instanciate using reflection. Get the constructor and call it.

There is now a rather elegant solution, made possible by (a) a new feature in .NET 4.0, and (b) Roslyn.

Collectible Assemblies

In .NET 4.0, you can specify AssemblyBuilderAccess.RunAndCollect when defining a dynamic assembly, which makes the dynamic assembly garbage collectible:

AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
    new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndCollect);

With vanilla .NET 4.0, I think that you need to populate the dynamic assembly by writing methods in raw IL.

Roslyn

Enter Roslyn: Roslyn lets you compile raw C# code into a dynamic assembly. Here's an example, inspired by these two blog posts, updated to work with the latest Roslyn binaries:

using System;
using System.Reflection;
using System.Reflection.Emit;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

namespace ConsoleApplication1
{
    public static class Program
    {
        private static Type CreateType()
        {
            SyntaxTree tree = SyntaxTree.ParseText(
                @"using System;

                namespace Foo
                {
                    public class Bar
                    {
                        public static void Test()
                        {
                            Console.WriteLine(""Hello World!"");
                        }
                    }
                }");

            var compilation = Compilation.Create("Hello")
                .WithOptions(new CompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(MetadataReference.CreateAssemblyReference("mscorlib"))
                .AddSyntaxTrees(tree);

            ModuleBuilder helloModuleBuilder = AppDomain.CurrentDomain
                .DefineDynamicAssembly(new AssemblyName("FooAssembly"), AssemblyBuilderAccess.RunAndCollect)
                .DefineDynamicModule("FooModule");
            var result = compilation.Emit(helloModuleBuilder);

            return helloModuleBuilder.GetType("Foo.Bar");
        }

        static void Main(string[] args)
        {
            Type fooType = CreateType();
            MethodInfo testMethod = fooType.GetMethod("Test");
            testMethod.Invoke(null, null);

            WeakReference weak = new WeakReference(fooType);

            fooType = null;
            testMethod = null;

            Console.WriteLine("type = " + weak.Target);
            GC.Collect();
            Console.WriteLine("type = " + weak.Target);

            Console.ReadKey();
        }
    }
}

In summary: with collectible assemblies and Roslyn, you can compile C# code into an assembly that can be loaded into an AppDomain, and then garbage collected (subject to a number of rules).

Well, you want to be able to edit things on the fly, right? that's your goal here isn't it?

When you compile assemblies and load them there's now way to unload them unless you unload your AppDomain.

You can load pre-compiled assemblies with the Assembly.Load method and then invoke the entry point through reflection.

I would consider the dynamic assembly approach. Where you through your current AppDomain say that you want to create a dynamic assembly. This is how the DLR (dynamic language runtime) works. With dynamic assemblies you can create types that implement some visible interface and call them through that. The back side of working with dynamic assemblies is that you have to provide correct IL yourself, you can't simply generate that with the built in .NET compiler, however, I bet the Mono project has a C# compiler implementation you might wanna check out. They already have a C# interpreter which reads in a C# source file and compiles that and executes it, and that's definitely handled through the System.Reflection.Emit API.

I'm not sure about the garbage collection here though, because when it comes to dynamic types I think the runtime doesn't release them because they can be referenced at any time. Only if the dynamic assembly itself is destroyed and no references exist to that assembly would it be reasonable to free that memory. If you're and re-generating a lot of code make sure that the memory is, at some point, collected by the GC.

If the language was Java the answer would be to use JRebel. Since it isn't, the answer is to raise enough noise to show there's demand for this. It might require some sort of alternate CLR or 'c# engine project template' and VS IDE working in coordination etc.

I doubt there's a lot of scenarios where this is "must have" but there's many in which it would save lot of time as you could get away with less infrastructure and quicker turnaround for things that aren't going to be used for long. (Yeah there's some who argue to over-engineer things because they'll be used 20+ years but the problem with that is when you need to do some massive change, it'll likely be as expensive as rebuilding the whole thing from scratch. So it comes down to whether to spend money now or later. Since it's not know for sure the project will become business critical later and might require large changes later anyhow, the argument to me is to use "KISS"-principle and have the complexities of live-editing in the IDE,CLR/runtime and so forth instead of building it into each app where it might be useful later. Certainly some defensive programming and practises will be needed to modify some live service using this sort of feature. As Erlang devs are said to be doing)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!