如何在 .NET Core 中卸载程序集/使其可收集?

Ste*_*ger 5 c# code-generation expression-evaluation assembly-loading roslyn

如何在 .NET Core 中卸载程序集?

注意:
.NET Core 不支持 AppDomain。

背景:
我必须动态评估用户生成的 VisualBasic 表达式。
为此,我使用 Roslyn 动态编译表达式。
我从 Roslyn 编译器生成的字节数组加载生成的程序集。
然后我创建一个实现抽象类的实例(因此我不必使用反射)。然后我调用抽象类的方法EvaluateExpression。
完成此操作后,我想卸载已加载的程序集(否则,我将享受内存泄漏的乐趣)。
因此,我在计算表达式后立即卸载程序集:

Parameters.AbstractEvaluator x = RoslynExpressionEvaluator.CreateEvaluator(expression, report.Code);
object value = x.EvaluateExpression();
x.LoadContext.Unload();
Run Code Online (Sandbox Code Playgroud)

(loadContext在生成时保存在抽象类中)

到目前为止一切正常,但是在x.LoadContext.Unload();,我得到

System.InvalidOperationException:“无法卸载不可收集的 AssemblyLoadContext。”

有可能解决这个问题吗?
如何使组件成为收藏品?
另外,我注意到我可以加载具有相同类名的程序集(如您所见,代码中没有命名空间)
这在多线程环境(又名网络)中表现如何?
我可以无限地加载动态生成的类的不同版本,直到机器用完 RAM 而没有出现故障吗?
或者为什么当加载同一个类两次时这会起作用?

using Microsoft.CodeAnalysis.Operations;

namespace ReportTester
{


    public static class RoslynExpressionEvaluator
    {
        // a utility method that creates Roslyn compilation
        // for the passed code. 
        // The compilation references the collection of 
        // passed "references" arguments plus
        // the mscore library (which is required for the basic
        // functionality).
        private static Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation
            CreateCompilationWithMscorlib
            (
                string assemblyOrModuleName,
                string code,
                Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions compilerOptions = null,
                System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.MetadataReference> references = null)
        {
            // create the syntax tree
            Microsoft.CodeAnalysis.SyntaxTree syntaxTree =
                Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.ParseSyntaxTree(code, null, "");

            // get the reference to mscore library
            Microsoft.CodeAnalysis.MetadataReference mscoreLibReference =
                Microsoft.CodeAnalysis.AssemblyMetadata
                    .CreateFromFile(typeof(string).Assembly.Location)
                    .GetReference();

            // create the allReferences collection consisting of 
            // mscore reference and all the references passed to the method
            System.Collections.Generic.List<Microsoft.CodeAnalysis.MetadataReference> allReferences =
                new System.Collections.Generic.List<Microsoft.CodeAnalysis.MetadataReference>() { mscoreLibReference  };

            if (references != null)
            {
                allReferences.AddRange(references);
            } // End if (references != null) 

            // create and return the compilation
            Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation compilation =
                Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation.Create
                (
                    assemblyOrModuleName,
                    new[] {syntaxTree},
                    options: compilerOptions,
                    references: allReferences
                );

            return compilation;
        } // End Function CreateCompilationWithMscorlib 


        // emit the compilation result into a byte array.
        // throw an exception with corresponding message
        // if there are errors
        private static byte[] EmitToArray( this Microsoft.CodeAnalysis.Compilation compilation )
        {
            using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
            {
                // emit result into a stream
                Microsoft.CodeAnalysis.Emit.EmitResult emitResult = compilation.Emit(stream);

                if (!emitResult.Success)
                {
                    // if not successful, throw an exception
                    foreach (Microsoft.CodeAnalysis.Diagnostic thisError in emitResult.Diagnostics)
                    { 
                        if(thisError.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
                            throw new System.Exception(thisError.GetMessage());
                    } // Next thisError 

                } // End if (!emitResult.Success) 

                // get the byte array from a stream
                return stream.ToArray();
            } // End Using stream 

        } // End Function EmitToArray 


        public static Parameters.AbstractEvaluator CreateEvaluator(string expression, string code)
        {
            try
            {
                // the main class Program contain static void Main() 
                // that calls A.Print() and B.Print() methods
                string mainProgramString = @"
Option Strict Off
Option Explicit Off
Option Infer On

Imports ReportTester.Parameters

imports System
Imports System.Collections.Generic
Imports Microsoft.VisualBasic.Strings
Imports Microsoft.VisualBasic.Interaction
Imports Microsoft.VisualBasic.Information



Public Class CodeImplementation
" + code + @"
End Class ' CodeImplementation




Public Class RsEval
    Inherits AbstractEvaluator


    Public Code As CodeImplementation


    Public Sub New()
        Me.New(New ParameterCollection)
    End Sub


    Public Sub New(ByVal allParameters As ParameterCollection)
        MyBase.New(allParameters)
        'code
    End Sub


    Public Overrides Function EvaluateExpression() As Object
        Return " + expression + @"
    End Function


End Class ' RsEval

";

                Microsoft.CodeAnalysis.MetadataReference sysRuntime =
                    Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(
                        typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location);

                Microsoft.CodeAnalysis.MetadataReference vbRuntime =
                    Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(Microsoft.VisualBasic.Constants)
                        .Assembly
                        .Location);

                Microsoft.CodeAnalysis.MetadataReference sysCorlib =
                    Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

                Microsoft.CodeAnalysis.MetadataReference sysConsole =
                    Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(System.Console).Assembly.Location);


                Microsoft.CodeAnalysis.MetadataReference reportParameters =
                    Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(ReportTester.Parameters.ParameterValue).Assembly.Location);


                Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions co = 
                    new Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions
                    (
                        Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary
                    );


                co.WithOptionStrict(Microsoft.CodeAnalysis.VisualBasic.OptionStrict.Off);
                co.WithOptionExplicit(false);
                co.WithOptionInfer(true);


                // create the Roslyn compilation for the main program with
                // ConsoleApplication compilation options
                // adding references to A.netmodule and B.netmodule
                Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation mainCompilation =
                    CreateCompilationWithMscorlib
                    (
                        "program",
                        mainProgramString,
                        // note that here we pass the OutputKind set to ConsoleApplication
                        compilerOptions: co,
                        references: new[] {sysRuntime, vbRuntime, sysCorlib, sysConsole, reportParameters }
                    );

                // Emit the byte result of the compilation
                byte[] result = mainCompilation.EmitToArray();

                // System.AppDomain temporaryAppDomain = System.AppDomain.CreateDomain("TemporaryAppDomain");
                // System.Reflection.Assembly assembly = temporaryAppDomain.Load(result);
                // not supported ... 

                // Load the resulting assembly into the domain. 
                System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(result);


                // here we get the Program type and 
                // call its static method Main()
                // to test the program. 

                // get the type Program from the assembly
                System.Type programType = assembly.GetType("RsEval");




                Parameters.AbstractEvaluator x = (Parameters.AbstractEvaluator)System.Activator.CreateInstance(programType);
                x.LoadContext = System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(assembly);


                // Get the static Main() method info from the type
                // System.Reflection.MethodInfo method = programType.GetMethod("EvaluateExpression");
                // invoke Program.Main() static method
                // object retValue = method.Invoke(null, null);

                // System.AppDomain.Unload(temporaryAppDomain);

                return x;
            }
            catch (System.Exception ex)
            {
                System.Console.WriteLine(ex.ToString());
                throw;
            }

            return null;
        } // End Sub Test 


    } // End Class RoslynExpressionEvaluator 


} // End Namespace ReportTester 
Run Code Online (Sandbox Code Playgroud)

抽象类:

Public MustInherit Class AbstractEvaluator

    Public Parameters As ParameterCollection

    Public LoadContext As System.Runtime.Loader.AssemblyLoadContext


    Public Sub New()
        Me.New(New ParameterCollection)
    End Sub


    Public Sub New(ByVal allParameters As ParameterCollection)
        Me.Parameters = allParameters
    End Sub

    Public Overridable Sub SetValue(ByVal parameterName As String, parameter As ReportParameter)
        Me.Parameters.Parameters(parameterName) = parameter
    End Sub

    Public Overridable Function GetValue(ByVal parameterName As String) As ReportParameter
        Return Me.Parameters.Parameters(parameterName)
    End Function


    Public Overridable Sub SetParameters(ByVal allParameters As ParameterCollection)
        Me.Parameters = Nothing
        Me.Parameters = allParameters
    End Sub


    Public MustOverride Function EvaluateExpression() As Object



    '  Public Parameters As ParameterCollection
    ' Public MustOverride Sub SetCompulsoryParameter()


End Class ' AbstractEvaluator


' example
Public Class ConcreteEvaluator
    Inherits AbstractEvaluator



    Public Sub New()
        Me.New(New ParameterCollection)
    End Sub


    Public Sub New(ByVal allParameters As ParameterCollection)
        MyBase.New(allParameters)
        'code
    End Sub



    'Public Overrides Sub SetCompulsoryParameter()
    'End Sub

    Public Overrides Function EvaluateExpression() As Object
        Dim expression As String = "System.DateTime.Now.AddDays(1+2+3).ToString(""dd.MM.yyyy"")" ' string expression would come from report, compiled with roslyn
        Return " + expression + @"
    End Function


End Class
Run Code Online (Sandbox Code Playgroud)

Ste*_*ger 4

嗯,问题是它卸载了整个上下文。
\n这意味着,如果我将程序集加载到默认上下文中,又名\n System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);
然后,在卸载时,它将卸载整个默认上下文。
\n绝对不好...==>

\n\n
\n

System.InvalidOperationException:“无法卸载不可收集的\n AssemblyLoadContext。”

\n
\n\n

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

\n\n
public class CollectibleAssemblyLoadContext : AssemblyLoadContext\n{\n    public CollectibleAssemblyLoadContext() : base(isCollectible: true)\n    { }\n\n    protected override Assembly Load(AssemblyName assemblyName)\n    {\n        return null;\n    }\n}\n\nbyte[] result = null; // Assembly Emit-result from roslyn\nSystem.Runtime.Loader.AssemblyLoadContext context = new CollectibleAssemblyLoadContext();\nSystem.IO.Stream ms = new System.IO.MemoryStream(result);\nSystem.Reflection.Assembly assembly = context.LoadFromStream(ms);\n\n\nSystem.Type programType = assembly.GetType("RsEval");\nMyAbstractClass eval = (MyAbstractClass) System.Activator.CreateInstance(programType);\neval.LoadContext = context;\neval.Stream = ms;\n// do something here with the dynamically created class "eval"\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后你可以说

\n\n
eval.LoadContext.Unload();\neval.Stream.Dispose();\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您将其放入抽象类的 IDisposable 接口中,那么如果您愿意,您可以直接使用 using 。

\n\n
using (Parameters.AbstractEvaluator x = RoslynExpressionEvaluator.CreateEvaluator(expression, report.Code))\n{\n    object value = x.EvaluateExpression();\n    System.Console.WriteLine(value);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以抽象类看起来像这样:

\n\n
Public MustInherit Class AbstractEvaluator\n    Implements IDisposable\n\n    Public Parameters As ParameterCollection\n\n    Public LoadContext As System.Runtime.Loader.AssemblyLoadContext\n    Public Stream As System.IO.Stream\n\n\n\n    Private disposedValue As Boolean \' Dient zur Erkennung redundanter Aufrufe.\n\n    Protected Overridable Sub Dispose(disposing As Boolean)\n        If Not disposedValue Then\n            If disposing Then\n                \' TODO: verwalteten Zustand (verwaltete Objekte) entsorgen.\n\n                If Me.LoadContext IsNot Nothing Then\n                    Me.LoadContext.Unload()\n                    Me.LoadContext = Nothing\n                End If\n\n                If Me.Stream IsNot Nothing Then\n                    Me.Stream.Dispose()\n                    Me.Stream = Nothing\n                End If\n\n            End If\n\n            \' TODO: nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalize() weiter unten \xc3\xbcberschreiben.\n            \' TODO: gro\xc3\x9fe Felder auf Null setzen.\n        End If\n        disposedValue = True\n    End Sub\n\n    \' TODO: Finalize() nur \xc3\xbcberschreiben, wenn Dispose(disposing As Boolean) weiter oben Code zur Bereinigung nicht verwalteter Ressourcen enth\xc3\xa4lt.\n    \'Protected Overrides Sub Finalize()\n    \'    \' \xc3\x84ndern Sie diesen Code nicht. F\xc3\xbcgen Sie Bereinigungscode in Dispose(disposing As Boolean) weiter oben ein.\n    \'    Dispose(False)\n    \'    MyBase.Finalize()\n    \'End Sub\n\n    \' Dieser Code wird von Visual Basic hinzugef\xc3\xbcgt, um das Dispose-Muster richtig zu implementieren.\n    Public Sub Dispose() Implements IDisposable.Dispose\n        \' \xc3\x84ndern Sie diesen Code nicht. F\xc3\xbcgen Sie Bereinigungscode in Dispose(disposing As Boolean) weiter oben ein.\n        Dispose(True)\n        \' TODO: Auskommentierung der folgenden Zeile aufheben, wenn Finalize() oben \xc3\xbcberschrieben wird.\n        \' GC.SuppressFinalize(Me)\n    End Sub\n\n\n\n    Public Sub New()\n        Me.New(New ParameterCollection)\n    End Sub\n\n\n    Public Sub New(ByVal allParameters As ParameterCollection)\n        Me.Parameters = allParameters\n    End Sub\n\n    Public Overridable Sub SetValue(ByVal parameterName As String, parameter As ReportParameter)\n        Me.Parameters.Parameters(parameterName) = parameter\n    End Sub\n\n    Public Overridable Function GetValue(ByVal parameterName As String) As ReportParameter\n        Return Me.Parameters.Parameters(parameterName)\n    End Function\n\n\n    Public Overridable Sub SetParameters(ByVal allParameters As ParameterCollection)\n        Me.Parameters = Nothing\n        Me.Parameters = allParameters\n    End Sub\n\n\n    Public MustOverride Function EvaluateExpression() As Object\n\n    \' Public Parameters As ParameterCollection\n    \' Public MustOverride Sub SetCompulsoryParameter()\n\n\nEnd Class \' AbstractEvaluator\n\n\n\' example\nPublic Class ConcreteEvaluator\n    Inherits AbstractEvaluator\n\n    Class SimplisticExampleCode\n\n        Public Function Tomorrow() As System.DateTime\n            Return System.DateTime.Now.AddDays(1)\n        End Function\n\n    End Class\n\n    Friend Code As SimplisticExampleCode\n\n\n    Public Sub New()\n        Me.New(New ParameterCollection)\n    End Sub\n\n\n    Public Sub New(ByVal allParameters As ParameterCollection)\n        MyBase.New(allParameters)\n        \'code\n        Me.Code = New SimplisticExampleCode\n    End Sub\n\n\n\n    \'Public Overrides Sub SetCompulsoryParameter()\n    \'End Sub\n\n    Public Overrides Function EvaluateExpression() As Object\n        \'Dim expression As String = "System.DateTime.Now.AddDays(1+2+3).ToString(""dd.MM.yyyy"")" \' string expression would come from report, compiled with roslyn\n        \'Return " + expression + @"\n        Return Code.Tomorrow().ToString("dd.MM.yyyy")\n    End Function\n\n\nEnd Class\n
Run Code Online (Sandbox Code Playgroud)\n