1

I want to offer the possibly to compile and run code (Csharp Classes) in .NET core 3 for purpose of scripting. The scripts (classes) shall be loaded from the file system and injected in an existing (static) assembly. https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (using AssemblyContext) seems to be a valid approach for this.

Is there a simpler solution (with less overhead) if I do not have the need to isolate the script code in an assembly?. (Debugging should be possible)

4
  • Are you asking how to debug code that is generated at runtime? And/or, are you asking how to inject new methods into a currently loaded assembly? Commented Feb 3, 2020 at 16:37
  • yes to both...I updated the question, hope it is clearer now. Commented Feb 3, 2020 at 17:46
  • Did you find a simpler solution? Commented Jul 7, 2020 at 14:35
  • no, not really. I consider now Iron Python as an alternative, since it supports .NET core now Commented Jul 7, 2020 at 17:34

1 Answer 1

2

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);
      }
    }
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

What is Family.Startup?
It would be any class from an assembly we want to use for the dynamic code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.