How can I unload an assemlby in .NET Core ?
Note:
.NET Core does not support AppDomains.
Background:
I have to evaluate user-generated VisualBasic expressions dynamically.
So to do this, I dynamically compile the expressions with Roslyn.
I load the resulting assemby from the byte array generated by the Roslyn compiler.
Then I create an instance that implements an abstract class (so I don't have to use reflection).
Then I call the method EvaluateExpression of the abstract class.
After this is done, I want to unload the loaded assembly (otherwise, I will have the pleasure of memory leaks).
So I unload the the assembly right after I evaluated the expression:
Parameters.AbstractEvaluator x = RoslynExpressionEvaluator.CreateEvaluator(expression, report.Code);
object value = x.EvaluateExpression();
x.LoadContext.Unload();
(loadContext is saved in the abstract class at generation)
Everything works fine so far, but at x.LoadContext.Unload();
, I get
System.InvalidOperationException: "Cannot unload non-collectible AssemblyLoadContext."
Is it possible to fix that ?
How can I make an assembly collectible ?
Also, I noticed I can load an assembly with the same class name (no namespace in code as you can see)
How does this fare in a multi-threading environment (aka web) ?
Can I just load and load different versions of the dynamically generated class ad infinitum, until the machine runs out of RAM with no malfunctions ?
Or why does this work at all when loading the same class twice ?
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
Abstract class:
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