There is a solution at https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/.
To save time, here is a variant of the program that runs a single file, supports LINQ, and classes from current project DLL :
Program.cs
using System.Linq;
using DynamicRun.Builder;
namespace DynamicRun
{
class Program
{
static void Main(string[] args)
{
var compiler = new Compiler();
var runner = new Runner();
byte[] compiled = compiler.Compile(args.FirstOrDefault());
runner.Execute(compiled, args[1..args.Length]);
}
}
}
Compiler.cs
using System;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
namespace DynamicRun.Builder
{
internal class Compiler
{
public byte[] Compile(string filepath)
{
var sourceCode = File.ReadAllText(filepath);
using (var peStream = new MemoryStream())
{
var result = GenerateCode(sourceCode).Emit(peStream);
if (!result.Success)
{
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());
}
return null;
}
Console.WriteLine("Compilation done without any error.");
peStream.Seek(0, SeekOrigin.Begin);
return peStream.ToArray();
}
}
private static CSharpCompilation GenerateCode(string sourceCode)
{
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
// Todo : to load current dll in the compilation context
MetadataReference.CreateFromFile(typeof(Family.Startup).Assembly.Location),
};
return CSharpCompilation.Create("Hello.dll",
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
}
}
}
Runner.cs
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace DynamicRun.Builder
{
internal class Runner
{
public void Execute(byte[] compiledAssembly, string[] args)
{
var assemblyLoadContext = LoadAndExecute(compiledAssembly, args);
for (var i = 0; i < 8 && assemblyLoadContext.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine(assemblyLoadContext.IsAlive ? "Unloading failed!" : "Unloading success!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference LoadAndExecute(byte[] compiledAssembly, string[] args)
{
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[] {args})
: entry.Invoke(null, null);
assemblyLoadContext.Unload();
return new WeakReference(assemblyLoadContext);
}
}
}
}