3

Trying to compile simple C# code at runtime on .NET Core but have this error:

System.PlatformNotSupportedException: 'Operation is not supported on this platform.'

on this line:

CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);

My code:

using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;

string code = @"
    using System;

    namespace First
    {
        public class Program
        {
            public static void Main()
            {
            " +
                "Console.WriteLine(\"Hello, world!\");"
                + @"
            }
        }
    }
";


CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();

parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;

CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);


if (results.Errors.HasErrors)
{
    StringBuilder sb = new StringBuilder();

    foreach (CompilerError error in results.Errors)
    {
        sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
    }

    throw new InvalidOperationException(sb.ToString());
}


Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("First.Program");
MethodInfo main = program.GetMethod("Main");


main.Invoke(null, null);
5
  • Yeah, sure. Did you actually check the official documentation for the CompileAssemblyFromSource method? What does it say? Commented Sep 20, 2022 at 23:31
  • 1
    @myskullcaveisadarkplace Yeah, sure. But I didn't notice anything that can help me. Can you say me what do you mean? Commented Sep 20, 2022 at 23:41
  • Read the remarks section of the method documentation... Commented Sep 20, 2022 at 23:43
  • @myskullcaveisadarkplace Noticed, thanks. Maybe you know the alternative? Commented Sep 20, 2022 at 23:43
  • Hmm, depends. There is Roslyn aka the .NET compiler platform SDK (learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/…, pmichaels.net/2021/08/29/compiling-code-files-in-c-using-roslyn). But it's quite a bit more advanced than the outdated CodeDomProvider, and i am not going to pretend that i have a good grasp on it. If that is too complex or time-consuming to master, perhaps there is some library out there that hides the complexities of Roslyn behind a simple(r) API just for some straightforward compiling of C# source code, but i don't know of any :-( Commented Sep 20, 2022 at 23:47

2 Answers 2

6

I recommend using the Roslyn compiler. You'll need to add references Microsoft.CodeAnalysis and Microsoft.CodeAnalysis.CSharp for the following example to work. Note, that the RoslynCompiler class loads the assembly dynamically. You can modify the class fairly easily to use a FileStream instead of a MemoryStream if you want to save the compilation to disk for reuse.

Sample Usage of RoslynCompiler Class (below)

    string code = @"
    using System;

    namespace First
    {
        public class Program
        {
            public static void Main()
            {
                Console.WriteLine(\"Hello, world!\");
            }
            public static void WithParams(string message)
            {
                Console.WriteLine(message);
            }
        }
    }
";

var compiler = new RoslynCompiler("First.Program", code, new[] {typeof(Console)});
var type = compiler.Compile();
    
type.GetMethod("Main").Invoke(null, null);
//result: Hellow World!

// pass an object array to the second null parameter to pass arguments
type.GetMethod("WithParams").Invoke(null, new object[] {"Hi there from invoke!"});
    //result: Hi from invoke

Roslyn Compiler Class (Quick and Dirty Example)

public class RoslynCompiler
{
    readonly CSharpCompilation _compilation;
    Assembly _generatedAssembly;
    Type? _proxyType;
    string _assemblyName;
    string _typeName;
    
    public RoslynCompiler(string typeName, string code, Type[] typesToReference)
    {
        _typeName = typeName;
        var refs = typesToReference.Select(h => MetadataReference.CreateFromFile(h.Assembly.Location) as MetadataReference).ToList();
        
        //some default refeerences
        refs.Add(MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll")));
        refs.Add(MetadataReference.CreateFromFile(typeof(Object).Assembly.Location));

       //generate syntax tree from code and config compilation options
        var syntax = CSharpSyntaxTree.ParseText(code);
        var options = new CSharpCompilationOptions(
            OutputKind.DynamicallyLinkedLibrary, 
            allowUnsafe: true,
            optimizationLevel: OptimizationLevel.Release);

        _compilation = CSharpCompilation.Create(_assemblyName = Guid.NewGuid().ToString(), new List<SyntaxTree> { syntax }, refs, options);
    }

    public Type Compile()
    {
        
        if (_proxyType != null) return _proxyType;
        
        using (var ms = new MemoryStream())
        {
            var result = _compilation.Emit(ms);
            if (!result.Success)
            {
                var compilationErrors = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error)
                    .ToList();
                if (compilationErrors.Any())
                {
                    var firstError = compilationErrors.First();
                    var errorNumber = firstError.Id;
                    var errorDescription = firstError.GetMessage();
                    var firstErrorMessage = $"{errorNumber}: {errorDescription};";
                    var exception = new Exception($"Compilation failed, first error is: {firstErrorMessage}");
                    compilationErrors.ForEach(e => { if (!exception.Data.Contains(e.Id)) exception.Data.Add(e.Id, e.GetMessage()); });
                    throw exception;
                }
            }
            ms.Seek(0, SeekOrigin.Begin);

            _generatedAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);

            _proxyType = _generatedAssembly.GetType(_typeName);
            return _proxyType;
        }
    }
}

Performance Tip

If performance matters, use delegates as opposed to Invoke as follows to achieve near pre-compiled throughput:

void Main()
{
    string code = @"OMITTED EXAMPLE CODE FROM SAMPLE ABOVE";

    var compiler = new RoslynCompiler("First.Program", code, new[] { typeof(Console) });
    var type = compiler.Compile();  
    
    // If perf matters used delegates to get near pre-compiled througput vs Invoke()
    var cachedDelegate = new DynamicDelegateCacheExample(type); 
    
    cachedDelegate.Main();
    //result: Hellow world!
    
    cachedDelegate.Main("Hi there from cached delegate!");
    //result: Hi there from cached delegate!


}
public class DynamicDelegateCacheExample
{
    delegate void methodNoParams();
    delegate void methodWithParamas(string message);
    private static methodNoParams cachedDelegate;
    private static methodWithParamas cachedDelegateWeithParams;

    public DynamicDelegateCacheExample(Type myDynamicType)
    {
        cachedDelegate = myDynamicType.GetMethod("Main").CreateDelegate<methodNoParams>();
        cachedDelegateWeithParams = myDynamicType.GetMethod("WithParams").CreateDelegate<methodWithParamas>();
    }
    
    public void Main() => cachedDelegate();

    public void Main(string message) => cachedDelegateWeithParams(message);
}

Sign up to request clarification or add additional context in comments.

1 Comment

See my answer for a different approach for System.Runtime & getting a MetadataReference from an assembly inside a published self contained .exe.
1

With .net core netstandard and publishing to a self contained exe there are a couple more tricks you'll need;

public static ModuleMetadata GetMetadata(this Assembly assembly)
{
    // based on https://github.com/dotnet/runtime/issues/36590#issuecomment-689883856
    unsafe
    {
        return assembly.TryGetRawMetadata(out var blob, out var len)
            ? ModuleMetadata.CreateFromMetadata((IntPtr)blob, len)
            : throw new InvalidOperationException($"Could not get metadata from {assembly.FullName}");
    }
}

#pragma warning disable IL3000
public static MetadataReference GetReference(this Assembly assembly)
    => (assembly.Location == "")
        ? AssemblyMetadata.Create(assembly.GetMetadata()).GetReference()
        : MetadataReference.CreateFromFile(assembly.Location);
#pragma warning restore IL3000

public static Assembly Compile(string source, IEnumerable<Type> references)
{
    var refs = new HashSet<Assembly>(){
        typeof(object).Assembly
    };
    foreach (var t in references)
        refs.Add(t.Assembly);

    foreach (var a in AppDomain.CurrentDomain.GetAssemblies()
        .Where(a => !a.IsDynamic
            && a.ExportedTypes.Count() == 0
            && (a.FullName.Contains("netstandard") || a.FullName.Contains("System.Runtime,"))))
        refs.Add(a);

    var options = CSharpParseOptions.Default
        .WithLanguageVersion(LanguageVersion.Latest);

    var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);

    var compilation = CSharpCompilation.Create("Dynamic",
        new[] { SyntaxFactory.ParseSyntaxTree(source, options) },
        refs.Select(a => a.GetReference()),
        compileOptions
    );

    using var ms = new MemoryStream();
    var e = compilation.Emit(ms);
    if (!e.Success)
        throw new Exception("Compilation failed");
    ms.Seek(0, SeekOrigin.Begin);

    var context = new AssemblyLoadContext(null, true);
    return context.LoadFromStream(ms);
}

// for dynamically implementing some interface;
public static C CompileInstance<C>(string source, IEnumerable<Type> references)
{
    var assembly = Compile(source, references);
    var modelType = assembly.DefinedTypes.Where(t => typeof(C).IsAssignableFrom(t)).Single();

    return (C)Activator.CreateInstance(modelType);
}

Comments

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.