CSharpScript. DynamicClass as globals getting CS0234 error - c#

I'm trying to build flexible script evaluator, which сould receive dynamic list of global variables.
I'm using DynamicClass from DynamicLinq lib for build global object. But I've got CS0234 error:
The type or namespace name 'Dynamic' does not exist in the namespace 'System.Linq'
My problem is that my global type instance generated dynamically, and I can't add dynamic assembly to references. InteractiveAssemblyLoader doesn't work too.
I know about DynamicExpression-s, but I'm trying to find pure solution.
var type = DynamicClassFactory.CreateType(
new List<DynamicProperty>()
{
new DynamicProperty("X", typeof(int)),
new DynamicProperty("Y", typeof(int))
});
var data = Activator.CreateInstance(type) as DynamicClass;
data.SetDynamicPropertyValue("X", 1);
data.SetDynamicPropertyValue("Y", 2);
using (var loader = new InteractiveAssemblyLoader())
{
loader.RegisterDependency(type.Assembly);
loader.RegisterDependency(typeof(ExpandoObject).Assembly);
var script = CSharpScript.Create<int>("X + Y",
ScriptOptions.Default
.AddReferences("System.Linq")
.AddReferences("System.Linq.Expressions")
.AddReferences("System.Linq.Dynamic.Core.DynamicClasses")
.AddImports("System.Linq")
.AddImports("System.Dynamic")
.AddImports("System.Linq.Dynamic.Core.DynamicClasses"),
globalsType: type,
assemblyLoader: loader
);
var t = script.Compile();
var p = await script.RunAsync(data);
}

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();
...

Get all methods from C# source code

I am looking forward to get all method names used in C# using following code:
CompilerParameters parameters = new CompilerParameters()
{
GenerateExecutable = false,
GenerateInMemory = true
};
var provider = new CSharpCodeProvider();
foreach (string path_file in files)
{
string source = File.ReadAllText(path_file);
Console.Out.WriteLine(source);
CompilerResults results = provider.CompileAssemblyFromSource(parameters, source);
if (results.Errors.HasErrors)
{
foreach (var error in results.Errors)
Console.Out.WriteLine(error.ToString());
return;
}
}
I am trying to read the source code in a variable source and passing this in CompileAssemblyFromSource method of CSharpCodeProvider class.
I am getting the following error under result variable.
c:\Users\username\AppData\Local\Temp\2rdqotiv.0.cs(3,14) : error CS0234: The type or namespace name 'Linq' does not exist in the namespace 'System' (are you missing an assembly reference?).
Can anyone please help me to solve this error? I am using MVS 15.
You have to add the referenced assemblies, for example:
parameters.ReferencedAssemblies.Add(typeof(Enumerable).Assembly.Location);
You can also add assemblies manually, as follows:
string exDir = $#"C:\WINDOWS\Microsoft.NET\Framework\v{ver}";
parameters.CompilerOptions = $"/lib:{exDir}";
And BTW, you could also use CompileAssemblyFromFile instead of having to open the files manually.

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.

Creating a method at runtime and System.IO Exception

Hello I have been trying to figure this out for a while now and cannot get it correct. I have found a few threads that do similar to what I want to do. but I keep getting a system.Io Assembly cannot be found compile and run the string that was created.
private bool CalculateBooleanExpression(string expression)
{
var classExpression = stringClass.Replace(ReplaceMe,expression);
var complierParameters = new CompilerParameters()
{
GenerateExecutable=false,
GenerateInMemory = true
};
var complier = new CSharpCodeProvider();
var compilerResults = complier.CompileAssemblyFromSource(complierParameters, classExpression);
//break point here compilerResults.CompiledAssembly is null
object typeInstance = compilerResults.CompiledAssembly.CreateInstance("BooleanEvaluator");
MethodInfo methodInfo = typeInstance.GetType().GetMethod("Calculate");
bool value = (bool)methodInfo.Invoke(typeInstance, new object[] { });
return true;
}
Could not load file or assembly 'file:///C:\Users\james.tays\AppData\Local\Temp\22ozhhme.dll' or one of its dependencies. The system cannot find the file specified.
can anyone tell me how to debug this so i can figure out where there error is?
EDIT:
While debugging I have found this error too.
error CS0116: A namespace cannot directly contain members such as fields or methods}

Categories

Resources