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);
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);
}