I'm a little confused as to how to go about adding references when using Roslyn to execute C# scripts.
I'm using the latest version of the API (1.2.20906.2), installed via NuGet.
I've searched lots of posts on Google, but there is significant change in the API since many of the examples I've found.
To illustrate what I'm trying to achieve:
using System;
using Roslyn.Scripting.CSharp;
namespace Test.ConsoleApp
{
public class Program
{
static void Main(string[] args)
{
new ScriptRunner().RunScripts();
}
}
public class ScriptRunner
{
public void RunScripts()
{
var engine = new ScriptEngine();
var session = engine.CreateSession();
session.AddReference("System");
session.AddReference("System.Linq");
// The following script runs successfully
session.Execute(#"using System;
var arr = new[] {1, 2, 6};
foreach (var i in arr)
{
if(i > 1)
{
Console.WriteLine(i);
}
}"
);
// The following script using Linq fails
session.Execute(#"using System;
using System.Linq;
var arr = new[] {1, 2, 6};
var arrResult = arr.Where(x => x > 1);
foreach (var i in arrResult)
{
Console.WriteLine(i);
}"
);
Console.ReadLine();
}
}
}
UPDATE - Included modification suggested in answer, plus referencing by path to DLL:
using System;
using Roslyn.Scripting.CSharp;
namespace Test.ConsoleApp
{
public class Program
{
static void Main(string[] args)
{
new ScriptRunner().RunScripts();
}
}
public class ScriptRunner
{
public void RunScripts()
{
var engine = new ScriptEngine();
var session = engine.CreateSession();
session.AddReference("System");
session.AddReference("System.Core"); // This reference is required to use Linq language features
session.AddReference("System.Linq");
session.Execute(#"using System;
using System.Linq;
var arr = new[] {1, 2, 6};
var arrResult = arr.Where(x => x > 1);
foreach (var i in arrResult)
{
Console.WriteLine(i);
}"
);
// Example use of adding reference to external project by path to dll
session.AddReference(#"E:\SVN\CSharpRoslynTesting\CSharpRoslynTesting\Test.ExternalLibraryTest\bin\Debug\Test.ExternalLibraryTest.dll");
session.Execute(#"using System;
using Test.ExternalLibraryTest;
var externalTestClass = new ExternalTestClass();
externalTestClass.TestOutput();
"
);
Console.ReadLine();
}
}
}
It is working for me, although I am using v1.2.20906.1. I didn't try your code, I just executed version I wrote for Roslyn presentation one month back.
Try to add System.Core:
session.AddReference("System.Core");
That's the only significant difference I can see so far.
Update: I just tried your code and missing reference I mentioned above was indeed the culprit. You even get nice exception:
(5,51): error CS1061: 'int[]' does not contain a definition for
'Where' and no extension method 'Where' accepting a first argument of
type 'int[]' could be found (are you missing a using directive or an
assembly reference?)
Related
The following program attempts to compile and execute a script of C#. However, I keep getting the output:
CS0103: The name 'Queryable' does not exist in the current context
CS1061: 'int[]' does not contain a definition for 'AsQueryable' and no accessible extension method 'AsQueryable' accepting a first argument of type 'int[]' could be found (are you missing a using directive or an assembly reference?)
Which assembly am I missing in refPaths? I assumed [System.Linq] would be all I need, which I then added through [typeof(Enumerable).GetTypeInfo().Assembly.Location].
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace CompileScript
{
class Program
{
static async Task Main(string[] args)
{
string script = #"using System;
using System.Linq;
namespace Test { class Program {
public static void Main() {
Console.WriteLine(""Testing a script..."");
int[] arr = new int[] { 1, 2, 3};
double avg = Queryable.Average(arr.AsQueryable());
Console.WriteLine(""Average = "" + avg);}}}";
var refPaths = new[] {
typeof(object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
typeof(Enumerable).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName
(typeof(System.Runtime.GCSettings).
GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
};
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = refPaths.Select(r => MetadataReference.
CreateFromFile(r)).ToArray();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions
(OutputKind.DynamicallyLinkedLibrary));
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.WriteLine("\t{0}: {1}", diagnostic.Id,
diagnostic.GetMessage());
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("Test.Program");
var instance = assembly.CreateInstance("Test.Program");
MethodInfo? methodInfo = type.GetMethod("Main");
methodInfo.Invoke(instance, null);
}
}
}
}
You shouldn't be creating the references manually like that. This is tricky to get right. Instead, you should use Basic.Reference.Assemblies NuGet package or a TFM-specific version of the package, e.g, Basic.Reference.Assemblies.Net60 which is open source on GitHub.
In fact, Roslyn itself uses this package for unit testing, e.g,:
https://github.com/dotnet/roslyn/blob/e9f1cd2980203eb9f7d9da81ed898f6f28a29b1f/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj#L19-L20
After adding the package to your project, the usage is as simple as adding the following using statement:
using Basic.Reference.Assemblies;
and creating your compilation as follows:
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: ReferenceAssemblies.Net50,
options: new CSharpCompilationOptions
(OutputKind.DynamicallyLinkedLibrary));
Then you don't need the following code:
var refPaths = new[] {
typeof(object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
typeof(Enumerable).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName
(typeof(System.Runtime.GCSettings).
GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
};
MetadataReference[] references = refPaths.Select(r => MetadataReference.
CreateFromFile(r)).ToArray();
We have an issue where simple c# code executed via CSharpCodeProvider doesn't work the same on running local command line and docker.
Code example is below, and it will not return any Types of the assembly when run on Roslyn, but works fine locally.
I have literally no idea how to debug this from here - any help welcome!
using System;
using Microsoft.CodeDom.Providers.DotNetCompilerPlatform;
using System.IO;
using System.Reflection;
using System.Text;
using System.CodeDom.Compiler;
namespace TestContainerIssue
{
class Program
{
static void Main(string[] args)
{
using (var codeProvider = new CSharpCodeProvider())
{
var compilerParameters = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true
};
compilerParameters.CompilerOptions = String.Format("/lib:\"{0}\"", Path.GetDirectoryName(Uri.UnescapeDataString((new UriBuilder(Assembly.GetExecutingAssembly().CodeBase)).Path)));
Console.WriteLine(compilerParameters.CompilerOptions);
compilerParameters.ReferencedAssemblies.Add("System.dll");
compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
CompilerResults compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, ExecutionWrapperCode);
if (compilerResults.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder();
foreach (CompilerError error in compilerResults.Errors)
errors.AppendLine(error.ErrorText);
throw new Exception(errors.ToString());
}
Console.WriteLine(compilerResults.PathToAssembly);
Assembly assembly = compilerResults.CompiledAssembly;
Console.WriteLine(assembly.FullName);
Console.WriteLine("Types:");
foreach (Type t in assembly.GetTypes())
{
Console.WriteLine(t);
}
Type type = assembly.GetType("Validation.Execution");
Console.WriteLine("Type:");
Console.WriteLine(type); //Empty when run in Docker (both mcr.microsoft.com/windows:1909 and mcr.microsoft.com/windows/servercore:ltsc2019
var methodInfo = type.GetMethod("Execute");
Console.WriteLine(methodInfo);
}
}
private const string ExecutionWrapperCode = #"
using System;
namespace Validation
{
public static class Execution
{
public static string Execute()
{
return ""test"";
}
}
}";
}
}
I tried below docker file (I tried two windows image: mcr.microsoft.com/windows:1909 and mcr.microsoft.com/windows/servercore:ltsc2019
FROM mcr.microsoft.com/windows/servercore:ltsc2019
ADD /bin/Debug /
ENTRYPOINT TestContainerIssue.exe
EDIT: I built the two dlls, and compared them in dotPeek - as you can see the one in Docker is missing the namespace. They are exactly the same bytes length though.
It turns out this line of code:
compilerParameters.CompilerOptions = String.Format("/lib:\"{0}\"", Path.GetDirectoryName(Uri.UnescapeDataString((new UriBuilder(Assembly.GetExecutingAssembly().CodeBase)).Path)));
was causing the problem. I don't know why it causes it and doesn't throw an error in compiler, but perhaps will help someone else.
Was
I have a string. For example
string str="if(a>b) {return a;} else {return b;}"
I want to evaluate or make function, say func(int a, int b) which will have the code of 'str'.
you may need to use CSharpCodeProvider as in this answer
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
class Program
{
static void Main(string[] args)
{
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = true;
CompilerResults results = csc.CompileAssemblyFromSource(parameters,
#"using System.Linq;
class Program {
public static void Main(string[] args) {
var q = from i in Enumerable.Range(1,100)
where i % 2 == 0
select i;
}
}");
results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
}
}
In general, this is not an easy thing to do, but the System.CodeDom namespace is where your journey will start.
Look at the following CodeProject article on the matter as a start: http://www.codeproject.com/Articles/26312/Dynamic-Code-Integration-with-CodeDom
The basics of it is as follows (as taken from the codeproject article):
private static Assembly CompileSource( string sourceCode )
{
CodeDomProvider cpd = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
//cp.ReferencedAssemblies.Add("ClassLibrary1.dll");
cp.GenerateExecutable = false;
// Invoke compilation.
CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);
return cr.CompiledAssembly;
}
The resultant assembly will have the class/method/code you are interested in, and then you can use reflection to call your method. Since your example just uses a code fragment, you will probably have to wrap it in a class/method before passing it to this method.
I hope that helps, but dynamic code generation in C# is not easy and this is just a start.
This code snippet was written to compile the code at run-time.
The compiled code works like any other program.
Reflection can be accessed with.
I want to do a little different things.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
class Program
{
static void Main(string[] args)
{
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = true;
CompilerResults results = csc.CompileAssemblyFromSource(parameters,
#"using System.Linq;
class Program {
public static void Main(string[] args) {
var q = from i in Enumerable.Range(1,100)
where i % 2 == 0
select i;
}
}");
results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
}
}
For Example :
My Code is Textbox1.Text="123"; it is in a file or in database.
There is a form.A Textbox in form.
I want to use string code as part of my program on runtime.
To do this, you can change the function name to string_Function instead of Main. Save the above code snippet as .cs file. Say Code_Snippet.cs. Now add this file to your MainProject. Wherever you want to use this code snippet you have to write line of code e.g. Code_Snippet.String_Function(TextBox.Text);
I tring to test a new dll that I've build for c#
private void button1_Click(object sender, EventArgs e)
{
String [] first = UserQuery.Get_All_Users();
//MessageBox.Show(first);
}
but I get the following error at String [] first = UserQuery.Get_All_Users();
An unhandled exception of type 'System.NullReferenceException' occurred in User_Query.dll
Additional information: Object reference not set to an instance of an object.
I been tring to figure this one out for hours but can't find any null varibles
I post my dll in case the dll is wrong
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace User_Query
{
public class UserQuery
{
public static string[] Get_All_Users()
{
string[] names = new string[10];
var path = string.Format("WinNT://{0},computer", Environment.MachineName);
using (var computerEntry = new DirectoryEntry(path))
{
var userNames = from DirectoryEntry childEntry in computerEntry.Children
where childEntry.SchemaClassName == "User"
select childEntry.Name;
byte i = 0;
foreach (var name in userNames)
{
Console.WriteLine(name);
names[i] = name;
i++;
}
return names;
}
}
}
}
There is a problem with your. path variable... since there should be \\ instead of //
The problem here turned out not to be the code but be VS2010 not loading the dll. This happen because I decided to change the program from using the dll from the debug to the release version but I did not clean the project after doing it and therefore the program was not correctly loading the dll. All that need to be done was clean the project