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 referencesnetstandard.dll 2.0
) and this assumes that your library, which references library withBaseClass
, should have a reference tonetstandard.dll 2.0
to correctrly resolve all corresponding types. So you should add reference on them (thenetstandard.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 frompath_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 acsproj
or asln
file you may useMsBuildWorkspace
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 retrieveCompilation
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
System.Drawing Out of Memory Exception
How to Remove an Xml Element from File
How to Extract Subscript/Superscript Properly from a PDF Using Itextsharp
Why Doesn't Mutex Get Released When Disposed
How to Define Implicit Conversions of Enums in C#
When Using a Settings.Settings File in .Net, Where Is the Config Actually Stored
Which Is Better Between a Readonly Modifier and a Private Setter
How to Make My Windows Form App Snap to Screen Edges
Sortedlist<>, Sorteddictionary<> and Dictionary<>
How to Clear Event Subscriptions in C#
Send JSON via Post in C# and Receive the JSON Returned
How Can One Generate and Save a File Client Side Using Blazor
C# Pass Lambda Expression as Method Parameter
Html.Enumdropdownlistfor: Showing a Default Text
Embedding a File Explorer Instance in a Windows Forms Application Form