Roslyn - CSharpCompilation - c#

I am using the CSharpCompilation class to compile a SyntaxTree where the root is a class declaration. I pass to the constructor a CSharpCompilationOptions object which contains my using statements.
My understanding is that the syntax tree will be compiled using the context of any using statements I pass through. However when trying to access a class which is defined in one of the 'usings' I pass to the options object I get an error saying it doesn't exist in the current context.
I am clearly doing something wrong. Anybody know what the list of usings is for when passed to the CSharpCompilationOptions class?
This is the code:
public static void TestMethod()
{
string source = #"public class Test
{
public static void TestMethod()
{
string str = Directory.GetCurrentDirectory();
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source);
List<string> usings = new List<string>()
{
"System.IO", "System"
};
List<MetadataFileReference> references = new List<MetadataFileReference>()
{
new MetadataFileReference(typeof(object).Assembly.Location),
};
//adding the usings this way also produces the same error
CompilationUnitSyntax root = (CompilationUnitSyntax)syntaxTree.GetRoot();
root = root.AddUsings(usings.Select(u => SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(u))).ToArray());
syntaxTree = CSharpSyntaxTree.Create(root);
CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, usings: usings);
CSharpCompilation compilation = CSharpCompilation.Create("output", new[] { syntaxTree }, references, options);
using (MemoryStream stream = new MemoryStream())
{
EmitResult result = compilation.Emit(stream);
if (result.Success)
{
}
}
}

So, it turns out that CSharpCompilationOptions.Usings is only ever examined in the compiler when compiling script files. If you trace through the references, it ends up getting used here, inside an if (inScript) check.
We probably need to document that better.

Related

How to reference another DLL in Roslyn dynamically-compiled code

I'm writing a project that dynamically compiles and executes c# code. The problem is that sometimes I want the code to call another DLL (for the sake of this sample I called it "ANOTHER.DLL"). It works fine in .Net 4.5, but fails in .Net Core and I can't figure out why. Any help is appreciated!
Code compiles successfully, but gives an error when the method is executed. Error is:
FileNotFoundException: Could not load file or assembly 'ANOTHER,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system
cannot find the file specified.
The ANOTHER.dll is located in the same /bin/debug folder, and is definitely accessible (code compiles!)
I noticed I can fix the issue by adding reference to ANOTHER.DLL to the project, but it defeats the purpose of dynamic compilation.
I tried this in .Net Core 2.0 - 3.1
ANOTHER.DLL is .Net Standard 2.0 (but same result with .Net Standard 2.1, or .Net Framework).
Also tried various versions of Microsoft.CodeAnalysis package, all giving me same error.
var eval = new Evaluator();
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List < MetadataReference > references = new List < MetadataReference > ();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
string ReferenceList = "";
ReferenceList += "netstandard.dll\n";
ReferenceList += "System.Runtime.dll\n";
ReferenceList += "ANOTHER.dll\n";
string[] assemblies = ReferenceList.Split('\n');
foreach(string a in assemblies) {
if (File.Exists(Path.Combine(assemblyPath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, a.Trim())));
}
else if (File.Exists(a.Trim())) {
string currDirectory = Directory.GetCurrentDirectory();
references.Add(MetadataReference.CreateFromFile(Path.Combine(currDirectory, a.Trim())));
}
else {
string exepath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (File.Exists(Path.Combine(exepath, a.Trim()))) {
references.Add(MetadataReference.CreateFromFile(Path.Combine(exepath, a.Trim())));
}
}
}
CSharpCompilation compilation = CSharpCompilation.Create("assembly", syntaxTrees: new[] {
syntaxTree
},
references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));
Assembly assembly;
using(var ms = new MemoryStream()) {
EmitResult result = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(ms.ToArray());
}
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x =>x.Name == "Hello");
var info = all.FirstOrDefault(x =>x.DeclaringType == type) ? ?all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] {}); // this is where the error occurs
Solution is based on my gist
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
string code = #"
using System;
namespace RoslynCompileSample
{
public class Test
{
public string Hello{
get {
//return ""Hello"";
var c = new ANOTHER.Class1();
return c.HelloWorld();
}
}
}
}";
var tree = SyntaxFactory.ParseSyntaxTree(code);
string fileName = "mylib.dll";
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "netstandard.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
var anotherDLLReference = MetadataReference.CreateFromFile(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
references.Add(anotherDLLReference);
var compilation = CSharpCompilation.Create(fileName)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(references)
.AddSyntaxTrees(tree);
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
EmitResult compilationResult = compilation.Emit(path);
if (compilationResult.Success)
{
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var type = assembly.GetType("RoslynCompileSample.Test");
var prop = type.GetProperties();
var all = prop.Where(x => x.Name == "Hello");
var info = all.FirstOrDefault(x => x.DeclaringType == type) ?? all.First();
var method = info.GetGetMethod();
object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");
object r = method.Invoke(obj, new object[] { });
}
}
}
}
To be fair, I have 0 idea how it works, since I am not familiar with working with assemblies on this level, but somehow I managed to get rid of exception.
Firstly, I checked AssemblyLoadContext.Default in the debugger. I noticed that reference to "ANOTHER.dll" is missing (although we previously added it)
Then I added AssemblyLoadContext.Default.LoadFromAssemblyPath(#"path to my ANOTHER.dll");. And when I checked it again - ANOTHER.dll was there.
Finally, we can see our hello world message
So the code I added is basically one line
// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");
var type = assembly.GetType("RoslynCompileSample.Test");
This works with both ANOTHER.dll targeting Standard 2.0 and .NET Core 3.1
Would be nice if someone smart actually told how it works.

Roslyn in memory compilation: CS0103: The name 'Console' does not exist in the current context

So i'm having this problem.
I'm trying to compile code in memory and adding namespace references by searching the syntax tree so i do not add them manually. Trying to simulate how Visual Studio maybe does it.
I'm a bit over my head in the compilation department. Even if i add a metadata reference to System while reading the syntax tree it does not find System.Console.
The key is that i want it to include the assemblies by itself, i do not want to add a "MetadataReference.CreateFromFile(....,"System.Console").
I explained the code below so that is clear what is happening.
class App
{
static void Main(string[] args)
{
//creating the syntax tree for the program
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
namespace ns{
using System;
public class App{
public static void Main(string[] args){
Console.Write(""dada"");
}
}
}");
//creating options that tell the compiler to output a console application
var options = new CSharpCompilationOptions(
OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true);
//creating the compilation
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), options: options);
//adding the syntax tree
compilation = compilation.AddSyntaxTrees(syntaxTree);
//getting the local path of the assemblies
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().ChildNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
//for each using directive add a metadatareference to it
foreach (var u in usings)
{
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, u.Name.ToString() + ".dll")));
}
//add the reference list to the compilation
compilation=compilation.AddReferences(references);
//compile
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}, {2}", diagnostic.Id, diagnostic.GetMessage(), diagnostic.Location);
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);
assembly.EntryPoint.Invoke(null, new object[] { new string[] { "arg1", "arg2", "etc" } });
}
}
}
}
In the .net core System.Console lives in the System.Console.dll. So you need to add reference on it
You need to add reference on the System.Runtime.dll to correctly resolve the predefined types: object, bool and so on
SyntaxNode.ChildNodes() returns only child, that means it doesn't return the descendents nodes, so if you want to get all UsingDirectiveSyntax you should change your logic. As one of way just use SyntaxNode.DescendantNodes()
After applying all suggestions you just get something likes this (The parts that didn't change will skipped):
...
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Console.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().DescendantNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
...

Cannot create a compilation in Roslyn from source code

For test purposes, I need to get a System.Reflection.Assembly from a string source which contains a source code. I am using Roslyn:
SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
CSharpCompilation compilation = CSharpCompilation.Create("TestCompilation", new[] { tree });
Assembly assembly = null;
using (var stream = new MemoryStream())
{
var emitResult = compilation.Emit(stream);
if (!emitResult.Success)
{
var message = emitResult.Diagnostics.Select(d => d.ToString())
.Aggregate((d1, d2) => $"{d1}{Environment.NewLine}{d2}");
throw new InvalidOperationException($"Errors!{Environment.NewLine}{message}");
}
stream.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(stream.ToArray());
}
As you can see my attempt here is to emit a CSHarpCompilation object so that I can get the Assembly later. I am trying to do this with:
var source = #"
namespace Root.MyNamespace1 {
public class MyClass {
}
}
";
Emit errors
But I fail at var emitResult = compilation.Emit(stream) and enter the conditional which shows the error. I get 1 warning and 3 errors:
Warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options.
(3,34): Error CS0518: Predefined type 'System.Object' is not defined or imported
(3,34): Error CS1729: 'object' does not contain a constructor that takes 0 arguments
Error CS5001: Program does not contain a static 'Main' method suitable for an entry point
So it seems I need to add reference to mscorelib and it also seems like I need to tell Roslyn that I want to emit a class library, not an executable assembly. How to do that?
You're missing a metadata reference to mscorlib and you can change the compilation options via CSharpCompilationOptions.
Create your compilation as follows:
var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }, options: options);
For creating a netstandard lib from not-netstandard code (in my case I create a netstandard lib from core3.1) the code should be
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] {
tree
},
references: new[] {
MetadataReference.CreateFromFile(#"C:\Users\YOURUSERNAME\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll"
},
options:
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
A crux here is the path.
As the host code is core3.1 one cannot use MetadataReference.CreateFromFile(typeof(object).Assembly.Location) as it references a core3.1 object and not a netcore2.0 object.
As referencing a nuget package (nowadays) downloads them to the %USERPROFILE%\.nuget\packages folder it can be loaded from there. This does not hold for any other user though so a different solution must be designed. One could utilise System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) but that probably won't hold for CI/CD.
Update:
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) does hold for CI/CD.
MetadataReference.CreateFromFile( Path.Combine(
UserProfilePath, ".nuget", "packages", "netstandard.library", "2.0.3", "build",
"netstandard2.0", "ref", "netstandard.dll"))
See LehmanLaidun builds.

Programmatically compiling source code using Roslyn

So I've been trying to compile a piece of code programmatically with Roslyn. For some reasons the references that I add just don't end up in the Compilation class. So when I look at the referenced assembly after I use 'AddReferences', the list is empty. Hence, when I try to emit, I get "Object" not defined in the diagnostics. Can anyone point me to the problem?
Microsoft.CodeAnalysis.SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
public static class Program
{
public static void Main()
{
System.Console.WriteLine(""Hello"");
}
}
");
string autoreferences = #"mscorlib.dll,System.Core.dll";
List<string> usings = new List<string>();
string netAssembliesDirectory = Path.GetDirectoryName(typeof(object).Assembly.Location);
var refs = new List<string>();
foreach (string reference in autoreferences.Split(','))
refs.Add(netAssembliesDirectory + "\\" + reference);
CSharpCompilation compilation = CSharpCompilation.Create("ConsoleTest")
.WithOptions(
new CSharpCompilationOptions(OutputKind.ConsoleApplication).WithUsings("System"))
.AddSyntaxTrees(syntaxTree);
compilation.AddReferences(refs.Where(r => r != "").Select(r => MetadataReference.CreateFromFile(r)));
var er = compilation.Emit(#"C:\" + "ConsoleTest");
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.

How do I execute a script with Roslyn in End User Preview

I'm trying to play around with the end user preview of roslyn and would like to execute a simple script. What I would like to do is something like:
static void Main(string[] args)
{
// Is this even valid?
var myScript = "int x = 5; int y = 6; x + y;";
// What should I do here?
var compiledScript = Something.Compile(myScript);
var result = compiledScript.Execute(myScript);
Console.WriteLine(result);
}
Can someone point to some resources and/or tell me which nuget packages to install to make this happen. I've installed the Microsoft.CodeAnalysis, but can't figure out if it doable with just that, I feel like I'm missing something.
The scripting APIs which would allow you to do this very easily were (temporarily) removed in the latest preview. You can still compile a script, emit and load the assembly and invoke its entry point by doing something along the lines of
public static class Program
{
public static void Main(string[] args)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
var defaultReferences = new[] { "mscorlib.dll", "System.dll", "System.Core.dll" };
var script = #"using System;
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine(""Hello {0}"", args[0]);
}
}";
// Parse the script to a SyntaxTree
var syntaxTree = CSharpSyntaxTree.ParseText(script);
// Compile the SyntaxTree to a CSharpCompilation
var compilation = CSharpCompilation.Create("Script",
new[] { syntaxTree },
defaultReferences.Select(x => new MetadataFileReference(Path.Combine(assemblyPath, x))),
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
using (var outputStream = new MemoryStream())
using (var pdbStream = new MemoryStream())
{
// Emit assembly to streams.
var result = compilation.Emit(outputStream, pdbStream: pdbStream);
if (!result.Success)
{
return;
}
// Load the emitted assembly.
var assembly = Assembly.Load(outputStream.ToArray(), pdbStream.ToArray());
// Invoke the entry point.
assembly.EntryPoint.Invoke(null, new object[] { new[] { "Tomas" } });
}
}
}
It will output Hello Tomas in the console :)
It appears that in the April 2014 release, scripting has been temporarily removed:
What happened to the REPL and hosting scripting APIs?
The team is reviewing the designs of these components that you saw in
previous CTPs, before re-introducing the components again. Currently
the team is working on completing the language semantics of
interactive/script code.

Categories

Resources