How to Compile a C# File with Roslyn Programmatically

How to compile a C# file with Roslyn programmatically?

I have created a sample for you to work with. You need to tweak it to use the run time for .Net 4.6 so that CSharp6 version is availble to you. I have added little details so that you can choose the options of compilations.

Changes required -
Change the path of runtime to target .Net 4.6
Change the LanguageVersion.Csharp5 to LanguageVersion.Csharp6 in below sample.

 class Program
{
private static readonly IEnumerable<string> DefaultNamespaces =
new[]
{
"System",
"System.IO",
"System.Net",
"System.Linq",
"System.Text",
"System.Text.RegularExpressions",
"System.Collections.Generic"
};

private static string runtimePath = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1\{0}.dll";

private static readonly IEnumerable<MetadataReference> DefaultReferences =
new[]
{
MetadataReference.CreateFromFile(string.Format(runtimePath, "mscorlib")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System.Core"))
};

private static readonly CSharpCompilationOptions DefaultCompilationOptions =
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release)
.WithUsings(DefaultNamespaces);

public static SyntaxTree Parse(string text, string filename = "", CSharpParseOptions options = null)
{
var stringText = SourceText.From(text, Encoding.UTF8);
return SyntaxFactory.ParseSyntaxTree(stringText, options, filename);
}

static void Main(string[] args)
{
var fileToCompile = @"C:\Users\DesktopHome\Documents\Visual Studio 2013\Projects\ConsoleForEverything\SignalR_Everything\Program.cs";
var source = File.ReadAllText(fileToCompile);
var parsedSyntaxTree = Parse(source, "", CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5));

var compilation
= CSharpCompilation.Create("Test.dll", new SyntaxTree[] { parsedSyntaxTree }, DefaultReferences, DefaultCompilationOptions);
try
{
var result = compilation.Emit(@"c:\temp\Test.dll");

Console.WriteLine(result.Success ? "Sucess!!" : "Failed");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.Read();
}

This would need little tweaks but it should give you desired results. Change it as you may wish.

C# Roslyn Compile - Use Nuget Package within dynamically compiled Code - compiles but throws an Exception at runtime (Could not load file or assembly)

I could solve the problem by using the event Handler

AppDomain.CurrentDomain.AssemblyResolve

With it i could resolve the Dependency.

Here the final result of the prototype code:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace Playground
{
public static class Program
{

public static void Main()
{
var test = new Test();
test.TestMethod();
}
}
public class Test

{

private const string pathToNugetDLL = @"C:\Data\packages\tinycsvparser.2.6.1\lib\net45\TinyCsvParser.dll";

private const string firstClass =
@"
using TinyCsvParser;

namespace A
{
public class MyClass
{
public int MyFunction()
{
CsvParserOptions csvParserOptions = new CsvParserOptions(true, ';');
return 1;
}
}
}";

public void TestMethod()
{
CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.CSharp7, DocumentationMode.Parse, SourceCodeKind.Regular);
SyntaxTree parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(firstClass, parseOptions);

List<string> defaultNamespaces = new List<string>() { };

//// Referenzen über Kommentare heraussuchen:
List<MetadataReference> defaultReferences = CreateMetadataReferences();

var encoding = Encoding.UTF8;

var assemblyName = Path.GetRandomFileName();
var symbolsName = Path.ChangeExtension(assemblyName, "pdb");
var sourceCodePath = "generated.cs";

var buffer = encoding.GetBytes(firstClass);
var sourceText = SourceText.From(buffer, buffer.Length, encoding, canBeEmbedded: true);

var syntaxTree = CSharpSyntaxTree.ParseText(
sourceText,
new CSharpParseOptions(),
path: sourceCodePath);

var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode;
var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, sourceCodePath, encoding);

CSharpCompilationOptions defaultCompilationOptions =
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Debug).WithPlatform(Platform.AnyCpu)
.WithUsings(defaultNamespaces);

CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { encoded },
references: defaultReferences,

options: defaultCompilationOptions
);

using (var assemblyStream = new MemoryStream())
using (var symbolsStream = new MemoryStream())
{
var emitOptions = new EmitOptions(
debugInformationFormat: DebugInformationFormat.Pdb,
pdbFilePath: symbolsName);

var embeddedTexts = new List<EmbeddedText> { EmbeddedText.FromSource(sourceCodePath, sourceText) };

EmitResult result = compilation.Emit(
peStream: assemblyStream,
pdbStream: symbolsStream,
embeddedTexts: embeddedTexts,
options: emitOptions);

if (result.Success)
{
Console.WriteLine("Complation succeeded!");
try
{
AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve;

Assembly nugetAssembly = Assembly.LoadFrom(pathToNugetDLL);

var assembly = Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray());
var type = assembly.GetType("A.MyClass");

MethodInfo method = type.GetMethod("MyFunction");

var fooInstance = Activator.CreateInstance(type);
method.Invoke(fooInstance, null);

}
catch (Exception ex)
{
int i = 0;
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= AppDomain_AssemblyResolve;
}
}
}
}

private Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] assemblyInfoSplitted = args.Name.Split(',');
string strSearchedForAssemblyName = assemblyInfoSplitted[0];

var fileInfo = new FileInfo(pathToNugetDLL);
var strAssemblyName = Regex.Replace(fileInfo.Name, ".dll", "", RegexOptions.IgnoreCase);
if (strSearchedForAssemblyName.ToLower() == strAssemblyName.ToLower())
{
//File.ReadAllBytes because DLL might be deleted afterwards in the filesystem
return Assembly.Load(File.ReadAllBytes(pathToNugetDLL));
}

throw new Exception($"Could not resolve Assembly '{strSearchedForAssemblyName}'.");
}

private static List<MetadataReference> CreateMetadataReferences()
{
string defaultPath = typeof(object).Assembly.Location.Replace("mscorlib", "{0}");

var metadatenReferences = new List<MetadataReference>()
{
MetadataReference.CreateFromFile(string.Format(defaultPath, "mscorlib")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "System")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "System.Data")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "System.Core")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "System.ComponentModel.DataAnnotations")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "System.Xml")),
MetadataReference.CreateFromFile(string.Format(defaultPath, "netstandard")),
};

string strExtraDll = pathToNugetDLL;
metadatenReferences.Add(MetadataReference.CreateFromFile(strExtraDll));

return metadatenReferences;
}
}

}

Programmatically compiling source code using Roslyn

Roslyn objects are immutable.

compilation.AddReferences() returns a new compilation instance with those references.

You're ignoring that new instance.

You need to call Emit() on the compilation instance that has your references.

Use Roslyn in NetStandard2.0 project to compile dynamically created code

  • It's not working because your library, which contains BaseClass, targets .netstandard2.0 (it means that this library references netstandard.dll 2.0) and this assumes that your library, which references library with BaseClass, should have a reference to netstandard.dll 2.0 to correctrly resolve all corresponding types. So you should add reference on them (the netstandard.dll for .net47 or the similar .netstandard.dll for .netcore2.2).
    (By the way, when you reference .netstandard2.0 from .net47 library you probably should add a couple of additional libraries as reference from path_to_visual_studio\MSBuild\Microsoft\Microsoft.NET.Build.Extensions)
  • Roslyn Compilation doesn't know anything about target framework and it should not know anything about it. Compilation works with trees and references (and with some options and metadata of references of course), so you should manually append references that will be required in compilation. (By the way, if you have a csproj or a sln file you may use MsBuildWorkspace that allow to get a ready compilation from the project or solution file, in the most of cases)
  • I suggest to create Compilation by hand if you know or can find out all references that will be required in compilation, else try to use Microsoft.CodeAnalysis.Workspaces.MSBuild to analyze .csproj or .sln files and then retrieve Compilation from them. Microsoft.Net.Compilers.Toolset just gives to you possibility to compile your project by compilers not are installed on your system, but are contained in this package.

How to compile successfully a tagged/marked class with Roslyn

With regard to this concrete question:

You need to make sure that all required references are included and that all references come from the same runtime (directory).

This works for me:

IEnumerable<MetadataReference> DefaultReferences = new[] {
MetadataReference.CreateFromFile(string.Format(runtimePath, "mscorlib")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System.Runtime")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System.Xml")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System.Xml.Serialization")),
MetadataReference.CreateFromFile(string.Format(runtimePath, "System.Xml.XmlSerializer"))
};

Note that the following code you had will most likely load the mscorlib assembly from some other directory than 'runtimePath' and thereby load an incompatible assembly.

MetadataReference.CreateFromFile(typeof(object).Assembly.Location)

Roslyn c# CSharpCompilation - Compiling Dynamic

Your dynamically-compiled code is not the same as your statically-compiled code. In your dynamically-compiled code, you've explicitly declared src as a dynamic. Your "hard-coded" example tries to treat is as a MyObject. You'd get the same problem if your hard-coded test looked like this:

    var src = new MyObject(map);
Console.WriteLine(src.Hello);

So you can fix this by casting your src as dynamic:

public static void MyMethod(MyObject src){
Console.WriteLine(((dynamic)src).Hello);
}

Or by declaring it as dynamic in the first place:

public static void MyMethod(dynamic src){
Console.WriteLine(src.Hello);
}


Related Topics



Leave a reply



Submit