LINQ Extensions not available inside CSharpCodeProvider - linq

I have a .NET application that can take a script written in C# and executes it internally. The scripts are parsed by the class listed below and then compiled. I find that whenever I try and use System.Xml.Linq in the C# script that is compiled I get a compile error and I am not sure why.
public static void CreateFunction(string scriptCode, BO.ObjectBO obj)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.ReferencedAssemblies.Add("System.Data.dll");
options.ReferencedAssemblies.Add("System.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add("System.Linq.dll");
options.ReferencedAssemblies.Add("System.Xml.Linq.dll");
options.GenerateExecutable = false;
options.GenerateInMemory = true;
CompilerResults results = provider.CompileAssemblyFromSource(options, scriptCode);
_errors = results.Errors;
if (results.Errors.HasErrors)
{
DataTable errorTable = BO.DataTableBO.ErrorTable();
foreach(CompilerError err in results.Errors)
{
DataRow dr = errorTable.NewRow();
dr["ErrorMessage"] = "Line "+ err.ErrorNumber.ToString() + " " + err.ErrorText;
errorTable.Rows.Add(dr);
}
return;
}
Type binaryFunction = results.CompiledAssembly.GetType("UserFunctions.BinaryFunction");
_methodInfo = binaryFunction.GetMethod("Function");
}
Here is the error message I get when I try and run a script that makes use of LINQ extensions inside the compiler.
'System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>' could be found (are you missing a using directive or an assembly reference?)
Does anyone see what I may be doing wrong? I am attempting to include System.Linq and System.Xml.Linq yet the compiler does not seem to be able to locate them.
Here is an example C# script I am trying to compile that makes use of LINQ extensions.
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Xml.Linq;
namespace CompilerTest
{
public class BinaryFunction
{
public static void Function()
{
string xmlData = #"<data>
<clients>
<client>
<clientId>1</clientId>
<clientName>Dell</clientName>
</client>
<client>
<clientId>2</clientId>
<clientName>Apple</clientName>
</client>
</clients>
</data>";
XDocument xDoc = XDocument.Parse(xmlData);
List<string> results = xDoc.Descendants("data")
.Descendants("client")
.Select(x => x.Element("clientName").Value)
.ToList<string>();
}
}
}
UPDATE: I confirmed that the following assemblies were in the GAC. System.Xml and System.Xml.Linq. I also added the compiler version to the constructor and I still get the same error.
CSharpCodeProvider(new Dictionary<String, String> { { "CompilerVersion", "v4.6.1" } })

After searching for related errors I found the solution. I needed to add System.Core as a referenced assembly.
options.ReferencedAssemblies.Add("System.Core.dll");
Once I did this then the LINQ assemblies were used and I was able to use LINQ extensions. So to be clear my new code is
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.ReferencedAssemblies.Add("System.Data.dll");
options.ReferencedAssemblies.Add("System.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add("System.Linq.dll");
options.ReferencedAssemblies.Add("System.Xml.Linq.dll");
options.ReferencedAssemblies.Add("System.Core.dll");
I am not sure why the reference to System.Core.dll is needed to be added as I would assume that it was referenced by default when creating a compiler instance but I guess not.

Related

Recompile assemblies to separate appdomains in NET 5

I have a NET 5.0 console application, from which I am trying to compile and execute external code BUT also be able to update the code, unload the previously created appdomain and re-compile everything.
This is my entire static class that handles code compilation and assembly loading
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using Microsoft.CodeAnalysis.Emit;
using System.Runtime.Loader;
namespace Scripting
{
public static class ScriptCompiler
{
public static Dictionary<string, AppDomain> _appDomainDict = new();
public static object CompileScript(string scriptpath)
{
var tree = SyntaxFactory.ParseSyntaxTree(File.ReadAllText(scriptpath));
//Adding basic references
List<PortableExecutableReference> refs = new List<PortableExecutableReference>();
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
// A single, immutable invocation to the compiler
// to produce a library
string hash_name = scriptpath.GetHashCode();
if (_appDomainDict.ContainsKey(hash_name))
{
AppDomain.Unload(_appDomainDict[hash_name]);
_appDomainDict.Remove(hash_name);
}
AppDomain new_domain = AppDomain.CreateDomain(hash_name);
_appDomainDict[hash_name] = new_domain;
var compilation = CSharpCompilation.Create(hash_name)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe:true))
.AddReferences(refs.ToArray())
.AddSyntaxTrees(tree);
MemoryStream ms = new MemoryStream();
EmitResult compilationResult = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
if (compilationResult.Success)
{
// Load the assembly
Assembly asm = new_domain.Load(ms.ToArray());
object main_ob = asm.CreateInstance("SomeClass");
ms.Close();
return main_ob;
}
else
{
foreach (Diagnostic codeIssue in compilationResult.Diagnostics)
{
string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}," +
$" Location: { codeIssue.Location.GetLineSpan()}," +
$" Severity: { codeIssue.Severity}";
Callbacks.Logger.Log(typeof(NbScriptCompiler), issue, LogVerbosityLevel.WARNING);
}
return null;
}
}
}
}
Its all good when I am trying load the assembly in the current domain and execute from the instantiated object. The problem with this case is that since I wanna do frequent updates to the code, even if I make sure that the assembly names are different. I'll end up loading a ton of unused assemblies to the current domain.
This is why I've been trying to create a new domain and load the assembly there. But for some reason I get a platform not supported exception. Is this not possible to do in NET 5? Are there any workarounds or am I doing something wrong here.
Ok, it turns out that AppDomain support for NET Core + is very limited and in particular there seems to be only one appdomain
On .NET Core, the AppDomain implementation is limited by design and
does not provide isolation, unloading, or security boundaries. For
.NET Core, there is exactly one AppDomain. Isolation and unloading are
provided through AssemblyLoadContext. Security boundaries should be
provided by process boundaries and appropriate remoting techniques.
Source: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-6.0
And indeed, when trying to use AssemblyLoadContext and create object instances through these contexts everything worked like a charm!
One last note is that if the created context is not marked as collectible, its not possible to unload it. But this can be very easily set during AssemblyLoadContext construction.

How to Reference .Net Core Library in a .Net Core Console Application

I am following this code example
I am on Visual Studio Community 2019 for Mac. I created a .Net Core - Class Library project and compiled to create the assembly file P1-ProgramStructure.dll.
I created another solution with program2.cs code. Please see the code below.
I renamed the .dll to acme.dll and copied the file into its directory.
Class library - .Net Core Project
Program1.cs
using System;
namespace Acme.Collections
{
public class Stack
{
Entry top;
public void Push(object data)
{
top = new Entry(top, data);
}
public object Pop()
{
if (top == null)
{
throw new InvalidOperationException();
}
object result = top.data;
top = top.next;
return result;
}
class Entry
{
public Entry next;
public object data;
public Entry(Entry next, object data)
{
this.next = next;
this.data = data;
}
}
}
}
.Net Core Console App
Program2.cs
using System;
using Acme.Collections;
class Example
{
static void Main()
{
Stack s = new Stack();
s.Push(1);
s.Push(10);
s.Push(100);
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
}
}
When I run the project, I get the error:
$ dotnet run
Program.cs(15,7): error CS0246: The type or namespace name 'Acme' could not be found (are you missing a using directive or an assembly reference?) [/Users/csarami/VisStudioProjects/cSharp Projects/Project2-ProjectStructure/Project2-ProjectStructure/Project2-ProjectStructure.csproj]
The build failed. Please fix the build errors and run again.
Make sure both projects have the same target framework

Error creating instance of C# class in F# script file

I have the following C# class that I would like to make use of in F#
using System;
using System.Collections.Generic;
using System.Text;
namespace DataWrangler.Structures
{
public enum Type { Trade = 0, Ask = 1, Bid = 2 }
public class TickData
{
public string Security = String.Empty;
public uint SecurityID = 0;
public object SecurityObj = null;
public DateTime TimeStamp = DateTime.MinValue;
public Type Type;
public double Price = 0;
public uint Size = 0;
public Dictionary<string, string> Codes;
}
}
I would like to create an instance of it in F#. The code I am using to do this is in an f# script file
#r #"C:\Users\Chris\Documents\Visual Studio 2012\Projects\WranglerDataStructures\bin\Debug\WranglerDataStructures.dll"
open System
open System.Collections.Generic;
open System.Text;
open DataWrangler.Structures
type tick = TickData // <- mouse over the "tick" gives me a tooltip with the class structure
// it bombs out on this line
let tickDataTest = tick(Security = "test", TimeStamp = DateTime(2013,7,1,0,0,0), Type = Type.Trade, Price = float 123, Size = uint32 10 )
The error I get is:
error FS0193: internal error: Could not load file or assembly 'file:///C:\Users\Chris\Documents\Visual Studio 2012\Projects\WranglerDataStructures\bin\Debug\WranglerDataStructures.dll' or one of its dependencies. An attempt was made to load a program with an incorrect format.
I have checked the file paths and they seem to be correct. I can mouse over the 'type tick' and it gives me the structure of the C# object. So It seems to be finding the C# code. Can anyone tell me what I am doing wrong here? Syntax? Still very new to C# -> F# introp
There are several things to check here:
Make sure that fsi.exe is running in a bit mode that is compatible with your WranglerDataStructures.dll. You run fsi.exe as a 64, or 32 bit process by setting a flag in the Visual Studio Options, under F# Tools -> F# Interactive -> 64-bit F# Interactive. You can usually avoid these types of problems by setting your C# assembly to compile as Any CPU.
Make sure that WranglerDataStructures.dll doesn't depend on other libraries that you are not referencing from F#. Either add the references in F#, or remove them from WranglerDataStructures.dll.
If these steps don't yield success try using the fuslogview.exe tool http://msdn.microsoft.com/en-us/library/e74a18c4.aspx to see exactly what reference is not being loaded.

LINQ not working on IEnumerable

I'm using Autofac (I've registered the base nuget package in a console app) and want to take a look at a list of registrations.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
// First, create your application-level defaults using a standard
// ContainerBuilder, just as you are used to.
var builder = new ContainerBuilder();
var appContainer = builder.Build();
appContainer.ComponentRegistry.Registrations.Where(x => true);
}
}
}
The problem is the line
appContainer.ComponentRegistry.Registrations.Where(x => true);
Here intellisense is not giving me the Where linq method however it does compile as far as I can tell without any warnings, errors in messages.
I tried this further down
IEnumerable<string> list = new List<string>();
list.Where(x => true);
And intellisense is working correctly and giving me all the standard list methods.
I've tried this in a few different apps from scratch and I'm getting the same behaviour.
Any ideas as to whats going on?
EDIT:
The following works and shows correctly in intellisense
IEnumerable<IComponentRegistration> test = new List<IComponentRegistration>();
test.Where(x => true);
I'm using
<package id="Autofac" version="3.0.1" targetFramework="net45" /> from nuget.
and hovering over the ComponentRegistrations gives
and in this case scope is defined as
ILifetimeScope _scope;
However I get the same thing if i try directly off this
var builder = new ContainerBuilder();
var appContainer = builder.Build();
appContainer.ComponentRegistry.Registrations.Where(x => true);
Also IComponentRegistry is defined as (in Autofac)
public interface IComponentRegistry : IDisposable
{
...
IEnumerable<IComponentRegistration> Registrations { get; }
...
}
Comment copied to answer.
If I've understood you correctly and your problem is that intellisense isn't working on the line
appContainer.ComponentRegistry.Registrations.Where(x => true);
You should probably try and disable your addons and see if that takes care of it as it's working fine for me. Since you say you had someone else confirm it, maybe start with any addons that your installations have in common.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
// First, create your application-level defaults using a standard
// ContainerBuilder, just as you are used to.
var builder = new ContainerBuilder();
var appContainer = builder.Build();
var registrations = appContainer.ComponentRegistry.Registrations.Where(x => x.Target.Equals("test"));
}
}
}
Try assigning the linq expression to a variable and see if it works

C#, ANTLR, ECMAScript grammar troubles

I'm trying to parse JavaScript (ECMASCript) with C#.
I found the following instruction on how to create new project:
http://www.antlr.org/wiki/pages/viewpage.action?pageId=557075
So I've downloaded ANTLRWorks, ANTLR v3, unpacked ANTLR, created a VS2010 project (.NET4), added references, checked and generated the grammar.
Then I recieved a lot of compilation error:
The type or namespace name 'AstParserRuleReturnScope' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'GrammarRule' could not be found (are you missing a using directive or an assembly reference?)
Stackoverlowed for them and got a solution: antlr c# errors when integrating into VS2008
So I've downloaded new runtime, overwrite the old one and recompiled the project and got
The name 'HIDDEN' does not exist in the current context d:\Workspace.1\ScriptParser\ScriptParser\TestLexer.cs
Ok, I've changed HIDDEN to Hidden as recommended at in the following conversation: [antlr-interest] How viable is the Csharp3 target? (more specific questions)
Now I'm trying to parse the input. I found a few examples and wrote the following code:
using Antlr.Runtime;
namespace ScriptParser
{
class Program
{
static void Main(string[] args)
{
var stream = new ANTLRStringStream("1+2");
var lexer = new TestLexer(stream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new TestParser(tokenStream);
// what exactly should be here???
}
}
}
My goal is to parser JavaScript file with ANTLR but it seems that it will be the not as easy as I thought...
Update:
As suggested in Why are antlr3 c# parser methods private? I've modified the Test.g grammar by adding the "public" modified before the expr rule:
public expr : mexpr (PLUS^ mexpr)* SEMI!
;
and then regenerated the code, replaced HIDDEN to Hidden (again) and modified the code as follows:
var stream = new ANTLRStringStream("1+2");
var lexer = new TestLexer(stream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new TestParser(tokenStream);
var result = parser.expr();
var tree = (CommonTree)result.Tree;
And not it is crashing on the line
root_0 = (object)adaptor.Nil();
in the following generated code
try { DebugEnterRule(GrammarFileName, "expr");
DebugLocation(7, 0);
try
{
// d:\\Workspace.1\\ScriptParser\\ScriptParser\\Test.g:7:13: ( mexpr ( PLUS ^ mexpr )* SEMI !)
DebugEnterAlt(1);
// d:\\Workspace.1\\ScriptParser\\ScriptParser\\Test.g:7:15: mexpr ( PLUS ^ mexpr )* SEMI !
{
root_0 = (object)adaptor.Nil();
DebugLocation(7, 15);
PushFollow(Follow._mexpr_in_expr31);
with the NullReferenceException message because the adapter is null.
I've resolved it by adding
parser.TreeAdaptor = new CommonTreeAdaptor();
Update 2:
So, finally I've started with my primary task: parse JavaScript.
ANTLR highlights the ECMAScript grammar by Chris Lambrou.
So I've generated lexer/parser and run it with the very simple JavaScript code:
var f = function () { };
and the parsing fails with the following output from tree.ToStringTree():
<error: var q = function () { };>
Your grammar rule says that there should be a semicolon at the end of the expression, but in you main function:
var stream = new ANTLRStringStream("1+2");
is missing a semicolon. Shouldn't it be "1+2;"?

Resources