如何删除/释放 .dll 程序集,由 Roslyn 发出

Posted

技术标签:

【中文标题】如何删除/释放 .dll 程序集,由 Roslyn 发出【英文标题】:How to delete/free .dll assembly, emitted with Roslyn 【发布时间】:2018-07-26 14:30:06 【问题描述】:

我正在尝试创建一个小型 Web 应用程序,它将接受带有代码和变量的 POST 请求,在 ASP.NET Core 服务器上编译它并打印结果。我做了一些研究,似乎使用 Roslyn 是唯一的方法。阅读指南后,我想出了下面提供的代码,发现在调用 GenerateAssembly() 方法后,负责创建程序集并调用它,.NET Core Host 进程锁定 mylib.dll 文件,直到服务器重新启动。我尝试使用 File.Delete() 删除文件,但它引发了拒绝访问异常。有没有办法解锁/删除该DLL?我的一个想法是创建以 GUID 为名称的 DLL,但我认为这不是很好的解决方案。

    public static object GenerateAssembly(string code, string[] param)
    
        var tree = SyntaxFactory.ParseSyntaxTree(code);
        string fileName = "mylib.dll"; //Guid.NewGuid().ToString() + ".dll";

        // Detect the file location for the library that defines the 
        // object type
        var systemRefLocation = typeof(object).GetTypeInfo().Assembly.Location;

        // Create a reference to the library
        var systemReference = MetadataReference.CreateFromFile(systemRefLocation);

        var compilation = CSharpCompilation.Create(fileName)
            .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
            .AddReferences(systemReference)
            .AddSyntaxTrees(tree);

        path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
        var compilationResult = compilation.Emit(path); // On 2nd call exception is raised on this line
        Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);

        object result = asm.GetType("RoslynCore.Helper").GetMethod("Calculate").
                Invoke(null, new object[]  param );
        return result;
    

【问题讨论】:

很抱歉,但带有 guid 文件名的想法很糟糕 :) 您将程序集加载到 appdomain,因此文件被锁定。因此,您需要加载到单独的 appdomain 然后销毁它,或者您可以尝试以不同的方式将程序集加载到当前 appdomain,例如 this answer 对 guid 的期望值也很高。非常感谢您的链接,这真的解决了它! 您应该将生成的程序集专门加载到另一个应用程序域中,这是您再次释放内存的唯一方法。也许 roslyn 可以将程序集直接发送到内存流,然后您甚至不必在加载之前将其写入磁盘。 【参考方案1】:

感谢 Reniuz 解决了这个问题,只是改变了 Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);Assembly asm = Assembly.Load(System.IO.File.ReadAllBytes(path));

【讨论】:

【参考方案2】:

亡灵术。 在完整的 .NET Framework 中,您曾经为此使用 AppDomains。 但是,.NET Core 不再使用 AppDomain,但在 .NET Core 3 中,您现在有了 LoadContexts。

但是...如果您只是将 assemlby 加载到默认上下文中,使用

System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);

你得到:

System.InvalidOperationException:“无法卸载非收藏品 AssemblyLoadContext。”

因此您必须在不同的上下文中加载程序集(类似于 AppDomain)

public class CollectibleAssemblyLoadContext : AssemblyLoadContext

    public CollectibleAssemblyLoadContext() : base(isCollectible: true)
     

    protected override Assembly Load(AssemblyName assemblyName)
    
        return null;
    


byte[] result = null; // Assembly Emit-result from roslyn
System.Runtime.Loader.AssemblyLoadContext context = new CollectibleAssemblyLoadContext();
System.IO.Stream ms = new System.IO.MemoryStream(result);
System.Reflection.Assembly assembly = context.LoadFromStream(ms);


System.Type programType = assembly.GetType("RsEval");
MyAbstractClass eval = (MyAbstractClass) System.Activator.CreateInstance(programType);
eval.LoadContext = context;
eval.Stream = ms;
// do something here with the dynamically created class "eval"

然后你可以说

eval.LoadContext.Unload();
eval.Stream.Dispose();

如果你把它放到抽象类的 IDisposable 接口中,那么你可以使用 using,如果你愿意的话。

【讨论】:

以上是关于如何删除/释放 .dll 程序集,由 Roslyn 发出的主要内容,如果未能解决你的问题,请参考以下文章

将程序集加载到子 AppDomain 并释放 dll 文件

C#.Net 如何动态加载与卸载程序集(.dll或者.exe)

[007] 详解 .NET 程序集

[.NET大牛之路 007] 详解 .NET 程序集

Roslyn - OutOfMemoryException 由于内存中加载的程序集

如何卸载已加载的程序集