Is there any shelf component that can be used to evaluate expressions on an object?
We would like to parse expressions like:
Func<T1, bool>
, Func<T1, T2, bool>
, Func<T1, T2, T3, bool>
Etc.
I realize that it is relatively easy to build an expression tree and evaluate it, but I would like to get around the overhead of doing compilation on the expression tree.
Is there any shelf component that can do this?
Is there any component that can parse C # expressions from a string and evaluate them? (Expression Services for C #, I think there is something similar for VB that WF4 uses)
Edit: We have specific models by which we need to evaluate expressions that are introduced by IT admins.
public class SiteModel
{
public int NumberOfUsers {get;set;}
public int AvailableLicenses {get;set;}
}
We would like them to introduce an expression like:
Site.NumberOfUsers > 100 && Site.AvailableLicenses < Site.NumberOfUsers
We would like to generate a Func that can be evaluated by passing in a SiteModel object.
Func<SiteModel, bool> (Site) => Site.NumberOfUsers > 100 && Site.AvailableLicenses < Site.NumberOfUsers
Also, the performance shouldn't be poor (but around 80-100 calls per second on a regular PC should be fine).
source to share
Thank you for your responses.
- Introducing Mono dependency in a product like ours (which has over 100,000 installations and has a long release cycle of 1-1.5 years) may not be the best option for us. It can also be overkill, as we only need to support simple expressions (with little or no nested expressions) and not the entire language.
- After using the code dom compiler, we noticed that it causes an application memory leak. While we could upload it to a separate app domain to get around this, this again might be overkill.
- The dynamic LINQ expression tree sample provided as part of the VS samples has many bugs and does not support type conversions when comparing ding (changing a string to int, double to int, long to int, etc.). The parsing for indexers is also broken. Although not used off the shelf, it does show promise for our use cases.
We decided to go with expression trees for now.
source to share
Mono.CSharp can evaluate expressions from strings and is very easy to use. The necessary links are supplied with the mono compiler and runtime. (In the iirc tools directory).
You need to specify Mono.CSharp.dll and the Mono C # compiler executable (mcs.exe).
Then set up an evaluator to know about your code if needed.
using Mono.CSharp;
...
Evaluator.ReferenceAssembly (Assembly.GetExecutingAssembly ());
Evaluator.Run ("using Foo.Bar;");
Then, evaluating the expressions is as easy as calling Evaluate.
var x = (bool) Evaluator.Evaluate ("0 == 1");
source to share
This method may be helpful to you - especially with regard to dependency views, since you only depend on infrastructure components.
EDIT : As defined by @Asti, this method creates dynamic assemblies which, unfortunately, cannot be unloaded due to design limitations of the .net Framework, so should be examined carefully before using it. This means that when the script is updated, the old assembly containing the previous version of the script cannot be unloaded from memory and will linger until the application or hosting services are restarted.
In a scenario where the frequency of changes to scripts is reduced, and where compiled scripts are cached and reused and not recompiled on every use, this memory leak can be safely tolerated IMO (this was the case for all of our uses of this method). Fortunately, in my experience, the memory footprint of the generated assemblies for typical scripts tends to be quite small.
If this is not acceptable, the scripts can be compiled on a separate AppDomain, which can be removed from memory, although this would require marshalling calls between domains (such as a WCF service named pipe) or perhaps an IIS hosting service where the offload occurs automatically after a period inactivity or memory threshold exceeded.)
End EDIT
First, you need to add a reference to your project Microsoft.CSharp
and add the following expressions using operators
using System.CodeDom.Compiler; // this is included in System.Dll assembly
using Microsoft.CSharp;
Then I add the following method:
private void TestDynCompile() {
// the code you want to dynamically compile, as a string
string code = @"
using System;
namespace DynCode {
public class TestClass {
public string MyMsg(string name) {
//---- this would be code your users provide
return string.Format(""Hello {0}!"", name);
//-----
}
}
}";
// obtain a reference to a CSharp compiler
var provider = CodeDomProvider.CreateProvider("CSharp");
// Crate instance for compilation parameters
var cp = new CompilerParameters();
// Add assembly dependencies
cp.ReferencedAssemblies.Add("System.dll");
// hold compiled assembly in memory, don't produce an output file
cp.GenerateInMemory = true;
cp.GenerateExecutable = false;
// don't produce debugging information
cp.IncludeDebugInformation = false;
// Compile source code
var rslts = provider.CompileAssemblyFromSource(cp, code);
if( rslts.Errors.Count == 0 ) {
// No errors in compilation, obtain type for DynCode.TestClass
var type = rslts.CompiledAssembly.GetType("DynCode.TestClass");
// Create an instance for the dynamically compiled class
dynamic instance = Activator.CreateInstance(type);
// Invoke dynamic code
MessageBox.Show(instance.MyMsg("Gerardo")); // Hello Gerardo! is diplayed =)
}
}
As you can see, you need to add boilerplate like wrapper class definition, inject assembly dependencies, etc.), but this is a really powerful technique that adds scripting capabilities with full C # syntax and is almost as fast as static code. (Invocation will be a little slower).
Assembly dependencies can reference your own project dependencies, so the classes and types defined in your project can be referenced and used within dynamic code.
Hope this helps!
The "component" you are talking about:
- It is required to understand the C # syntax (to parse your input string)
- It is necessary to understand the semantics of C # (where to perform implicit int-> double conversions, etc.).
- Need to generate IL code
Such a "component" is called a C # compiler.
-
The existing Microsoft C # compiler is a bad option because it runs in a separate process (thus increasing compilation time since all metadata needs to be loaded into this process) and can only collect complete assemblies (and .NET assemblies cannot be unloaded without unloading the entire AppDomain, thereby leaking memory). However, if you can live with these limitations, this is an easy solution - see sgorozco's answer.
-
The future Microsoft C # compiler (Roslyn project) will be able to do what you want, but that's still some time in the future. I assume it will be released with the next VS after VS11, i.e. with C # 6.0.
-
The Mono C # compiler (see Response to H answer) can do what you want, but I don't know if it supports code unloading or if there will be a memory leak.
-
Scan your own. You know which subset of C # you need to support, and there are separate components available for the various "needs" above. For example, NRefactory 5 can parse C # code and parse semantics. Expression trees make it much easier to generate IL code. You can write a converter from NRefactory ResolveResults to expression trees that will most likely solve your problem in less than 300 lines of code. However, NRefactory reuses a significant chunk of the Mono C # compiler in its parser - and if you accept this large dependency, you can go with option 3 as well.
source to share
Perhaps you can use LUA Scripts as input. The user enters an LUA expression and you can parse and execute it using the LUA engine. You can wrap the input in some other LUA code if needed before interpreting it, and I'm not sure about the performance. But 100 calls / s isn't that much.
Expression evaluation is always a security issue. So take care of that too. You can use LUA in C #
Another way would be to compile C # code containing the input expression in the class. But here you will get one assembly per request. I think .net 4.0 can unload assemblies, but older versions of .net cannot. therefore this solution may not scale well. The workaround could be a native process that is restarted every X requests.
source to share