简体   繁体   中英

.net reload assembly at runtime

I searched a lot about reloading an assembly at runtime in .NET. The only method I can find is using another AppDomain. But this makes things really complicated. And it is almost impossible in my case because the classes in the assembly which is going to be loaded at runtime do not inherit from MarshalByRefObject. I've looked at Unity game engine. The editor builds the components at runtime and just uses the compiled assembly. How is it possible?

I have done this using MEF. I am not sure if it is an option for you, but it works well. However even with MEF it is somewhat complicated.

On my case I am loading all the dll from a particular folder.

These are the setup classes.

public static class SandBox
{
    public static AppDomain CreateSandboxDomain(string name, string path, SecurityZone zone)
    {
        string fullDirectory = Path.GetFullPath(path);
        string cachePath = Path.Combine(fullDirectory, "ShadowCopyCache");
        string pluginPath = Path.Combine(fullDirectory, "Plugins");

        if (!Directory.Exists(cachePath))
            Directory.CreateDirectory(cachePath);

        if (!Directory.Exists(pluginPath))
            Directory.CreateDirectory(pluginPath);


        AppDomainSetup setup = new AppDomainSetup
        {
            ApplicationBase = fullDirectory,
            CachePath = cachePath,
            ShadowCopyDirectories = pluginPath,
            ShadowCopyFiles = "true"
        };

        Evidence evidence = new Evidence();
        evidence.AddHostEvidence(new Zone(zone));
        PermissionSet permissions = SecurityManager.GetStandardSandbox(evidence);

        return AppDomain.CreateDomain(name, evidence, setup, permissions);
    }
}

public class Runner : MarshalByRefObject
{
    private CompositionContainer _container;
    private DirectoryCatalog _directoryCatalog;
    private readonly AggregateCatalog _catalog = new AggregateCatalog();


    public bool CanExport<T>()
    {
        T result = _container.GetExportedValueOrDefault<T>();
        return result != null;
    }

    public void Recompose()
    {
        _directoryCatalog.Refresh();
        _container.ComposeParts(_directoryCatalog.Parts);
    }

    public void RunAction(Action codeToExecute)
    {
        MefBase.Container = _container;
        codeToExecute.Invoke();
    }

    public void CreateMefContainer()
    {
        RegistrationBuilder regBuilder = new RegistrationBuilder();
        string pluginPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
        _directoryCatalog = new DirectoryCatalog(pluginPath, regBuilder);
        _catalog.Catalogs.Add(_directoryCatalog);

        _container = new CompositionContainer(_catalog, true);
        _container.ComposeExportedValue(_container);
        Console.WriteLine("exports in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName);
    }
}

Here is the actual code.

AppDomain domain = SandBox.CreateSandboxDomain($"Sandbox Domain_{currentCount}", directoryName, SecurityZone.MyComputer);
            foreach (FileInfo dll in currentDlls)
            {
                string path = Path.GetFullPath(Path.Combine(directoryName, dll.Name));
                if (!File.Exists(path))
                    File.Copy(dll.FullName, Path.Combine(directoryName, dll.Name), true);

                domain.Load(typeof(Runner).Assembly.FullName);
            }

You can get the domain back by doing this.

Runner runner = (Runner) domain.CreateInstanceAndUnwrap(typeof(Runner).Assembly.FullName, typeof(Runner).FullName);
runner.CreateMefContainer(); // or runner.Recompose();

You will need to call your code like this.

runner.RunAction(() =>
                {
                    IRepository export = MefBase.Resolve<IRepository>();
                    export?.Get("123");
                    Console.WriteLine("Executing {0}", export);
                });

Generally speaking, you cannot reload assembly within the same AppDomain. You can create one dynamically and load it (and it will sit in your AppDomain forever), you can load another almost-but-not-quite-the-same copy of your assembly, but once the assembly is in AppDomain, it's stuck.

Imagine that a library assembly defines SomeType and your client code just created an instance. If you unload the library, what is supposed to happen with this instance? If the library is in another AppDomain, the client will use a proxy with a well-defined (in MarshalByRefObject) behaviour (to go zomby with the domain unload and throw exceptions foreverafter). Supporting unloading of arbitrary types would have made the runtime incredibly complicated, unpredictable or both.

As for Unity, see this discussion . Quote:

An "assembly reaload" sounds like some kind of quick update check but in fact the whole scripting environment reloads. This will destroy everything in the managed land. Unity can recover from this by using it's serialization system. Unity serializes the whole scene before the reload, then recreates everything and deserializing the whold scene. Of course only things which can be serialized will "survive" this process.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM