I have been experimenting with the C# dynamic compilation as described in Laurent's excellent blog: https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (Merci Laurent!!)
I copied and pasted the code into a single file and all in the Main method to understand the control flow better.
I have however been unable to work out why the unloading of the DLL consistently fails (i.e. the WeakReference is still live). Laurent's code (as published on GitHub) does unload the DLL while my copy-pasted monolithic code does not.
Could someone help me spot where I have gone wrong?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace CoreCompile
{
public class CompilerTest
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
string sourcePath = args.Length > 0 ? args[0] : @"D:\DynamicRun\Sources\DynamicProgram.cs";
string sourceCode = File.ReadAllText(sourcePath);
string assemblyPath = Path.ChangeExtension(Path.GetFileNameWithoutExtension(sourcePath), "DLL");
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
};
Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList()
.ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
var csCompilation = CSharpCompilation.Create(assemblyPath,
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
WeakReference assemblyLoadContextWeakRef = null;
using (var peStream = new MemoryStream())
{
var result = csCompilation.Emit(peStream);
if (result.Success)
{
Console.WriteLine("Compilation done without any error.");
peStream.Seek(0, SeekOrigin.Begin);
var compiledAssembly = peStream.ToArray();
string[] arguments = new[] { "France" };
using (var asm = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
var assembly = assemblyLoadContext.LoadFromStream(asm);
var entry = assembly.EntryPoint;
_ = entry != null && entry.GetParameters().Length > 0
? entry.Invoke(null, new object[] { arguments })
: entry.Invoke(null, null);
assemblyLoadContext.Unload();
assemblyLoadContextWeakRef = new WeakReference(assemblyLoadContext);
} // using
}
else
{
Console.WriteLine("Compilation done with error.");
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
foreach (var diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
} // using
if (assemblyLoadContextWeakRef != null)
{
for (var i = 0; i < 8 && assemblyLoadContextWeakRef.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine(assemblyLoadContextWeakRef.IsAlive ? "Unloading failed!" : "Unloading success!");
}
} // Main
} // class
internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext()
: base(true)
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
return null;
}
}
} // namespace