|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThere are already quite a few articles about dynamic expression evaluation using .NET. What I want to show here is a new twist on the idea by using the The good thing about compiled code is it's fast. However, there are several problems with it. It seems to take quite a bit of time to compile compared to parsing. So if the expression is constantly changing then it won't be a good performer. But if the expression is something to be compiled once and called many times then its performance can't be beat. So this is the problem I'm trying to solve; given a set of expressions I need to execute them quickly for a given set of values or row of data. Then be able to do the same for a data set by repeating for each row in the set. BackgroundSome useful articles/sites I've found: Parsing the IL of a Method Body It would be a good idea to look at some of these to get an idea of how this article differs or on what concepts it's based. So a problem with CodeDom is that compiling causes the resultant assembly to be loaded into the current AppDomain. Since in .NET you can't unload an assembly you would end up with a lot of assemblies since a new one is created for each new expression. To keep the generated assembly from being loaded right away another AppDomain can be created where code the is generated. But when we want to execute the expression the assembly still has to be loaded. So what can we do to get around this problem? Enter By now I'm sure you just looked in MSDN to find out more about it. I knew I didn't want to write a parser and I sure didn't want to spend a lot of time with writing IL code. The bright idea comes from the Turn MethodInfo to DynamicMethod article linked above. Basically reflection can be used to get the IL code of a compiled method and generate a Using the codeTo turn a public class FunctionClass
{
//some instance data, could be another object
//maybe a datarow or custom data object
public double X;
public double Y;
public double Z;
//method that can be called
public double Round(double number)
{
return Math.Round(number);
}
}
CodeDom CompilerSo to compile a C# expression that will have access to the methods on this class I'm going to make the compiled method on a class that inherits from /// <summary>
/// Expression compiler using the C# language
/// </summary>
public class CSharpExpressionCompiler : BaseExpressionCompiler,
IExpressionCompiler
{
/// <summary>
/// Compiles the expression into an assembly and returns the method code
/// for it.
/// It should compile the method into a class that inherits from the
/// functionType.
/// </summary>
/// <param name="expression">expression to be
/// compiled</param>
/// <param name="functionType">Type of the function class
/// to use</param>
/// <param name="returnType">Return type of the method to
/// create</param>
/// <returns>DynamicMethodState - A serialized version of the
/// method code</returns>
public DynamicMethodState CompileExpression(string expression,
Type functionType, Type returnType)
{
DynamicMethodState methodState;
//use CodeDom to compile using C#
CodeDomProvider codeProvider =
CodeDomProvider.CreateProvider("CSharp");
CompilerParameters loParameters = new CompilerParameters();
//add assemblies
loParameters.ReferencedAssemblies.Add("System.dll");
loParameters.ReferencedAssemblies.Add(functionType.Assembly.Location);
//don't generate assembly on disk and treat warnings as errors
loParameters.GenerateInMemory = true;
loParameters.TreatWarningsAsErrors = true;
//set namespace of dynamic class
string dynamicNamespace = "ExpressionEval.Functions.Dynamic";
//set source for inherited class - need to change to use CodeDom
//objects instead
string source = @"
using System;
using {5};
namespace {6}
{{
public class {0} : {1}
{{
public {2} {3}()
{{
return {4};
}}
}}
}}
";
//set source code replacements
string className = "Class_" + Guid.NewGuid().ToString("N");
string methodName = "Method_" + Guid.NewGuid().ToString("N");
string returnTypeName = returnType.FullName;
//check for generic type for return
....
//format codestring with replacements
string codeString = string.Format(source, className,
functionType.FullName, returnTypeName,
methodName, expression, functionType.Namespace, dynamicNamespace);
//compile the code
CompilerResults results =
codeProvider.CompileAssemblyFromSource(loParameters, codeString);
if (results.Errors.Count > 0)
{
//throw an exception for any errors
throw new CompileException(results.Errors);
}
else
{
//get the type that was compiled
Type dynamicType = results.CompiledAssembly.GetType(
dynamicNamespace + "." + className);
//get the MethodInfo for the compiled expression
MethodInfo dynamicMethod = dynamicType.GetMethod(methodName);
//get the compiled expression as serializable object
methodState = GetMethodState(dynamicMethod);
}
return methodState;
}
}
The /// <summary>
/// A base expression compiler. MarshalByRef so it can be used across
/// AppDomains.
/// </summary>
public abstract class BaseExpressionCompiler : MarshalByRefObject
{
/// <summary>
/// Converts a MethodInfo into a serialized version of it.
/// </summary>
/// <param name="dynamicMethod">The method for which to
/// create a DynamicMethod for</param>
/// <returns>DynamicMethodState - serialized version of a
/// method.</returns>
protected DynamicMethodState GetMethodState(MethodInfo dynamicMethod)
{
DynamicMethodState methodState = new DynamicMethodState();
//IL info from method
MethodBody methodIlCode = dynamicMethod.GetMethodBody();
//get code bytes and other method properties
methodState.CodeBytes = methodIlCode.GetILAsByteArray();
methodState.InitLocals = methodIlCode.InitLocals;
methodState.MaxStackSize = methodIlCode.MaxStackSize;
//get any local variable information
IDictionary<int, LocalVariable> locals = new SortedList<int,
LocalVariable>();
foreach (LocalVariableInfo localInfo in methodIlCode.LocalVariables)
{
locals.Add(localInfo.LocalIndex, new
LocalVariable(localInfo.IsPinned,
localInfo.LocalType.TypeHandle));
}
methodState.LocalVariables = locals;
TokenOffset tokenOffset = new TokenOffset();
//get metadata token offsets
IlReader reader = new IlReader(methodState.CodeBytes,
dynamicMethod.Module);
tokenOffset.Fields = reader.Fields;
tokenOffset.Methods = reader.Methods;
tokenOffset.Types = reader.Types;
tokenOffset.LiteralStrings = reader.LiteralStrings;
methodState.TokenOffset = tokenOffset;
return methodState;
}
}
The compiler class inherits from DynamicMethod DelegateThe /// <summary>
/// Implements a Delegate Factory for compiled expressions
/// </summary>
public class ExpressionDelegateFactory : IExpressionDelegateFactory
{
private ExpressionLanguage m_language;
/// <summary>
/// Delegate Factory for a Language
/// </summary>
/// <param name="language"></param>
public ExpressionDelegateFactory(ExpressionLanguage language)
{
m_language = language;
}
/// <summary>
/// Compiles an expression and returns a delegate to the compiled code.
/// </summary>
/// <typeparam name="R">The return type of the
/// expression</typeparam>
/// <typeparam name="C">The type of the function
/// class</typeparam>
/// <param name="expression">Expression to
/// evaluate</param>
/// <returns>ExecuteExpression<R, C> - a delegate that calls
/// the compiled expression</returns>
public ExecuteExpression<R, C> CreateExpressionDelegate<R,
C>(string expression)
{
ExecuteExpression<R, C> expressionDelegate = null;
DynamicMethodState methodState;
//create the compiled expression
methodState = CreateExpressionMethodState<R, C>(expression);
if (methodState != null && methodState.CodeBytes != null)
{
//get a dynamic method delegate from the method state
expressionDelegate = CreateExpressionDelegate<R,
C>(methodState);
}
return expressionDelegate;
}
/// <summary>
/// Compiles an expression and returns a DynamicMethodState
/// </summary>
/// <typeparam name="R">The return type of the
/// expression</typeparam>
/// <typeparam name="C">The type of the function
/// class</typeparam>
/// <param name="expression">Expression to
/// evaluate</param>
/// <returns>DynamicMethodState - serialized version of the
/// compiled expression</returns>
public DynamicMethodState CreateExpressionMethodState<R,
C>(string expression)
{
DynamicMethodState methodState;
IExpressionCompiler compiler;
//create an AppDomain
AppDomainSetup loSetup = new AppDomainSetup();
loSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
AppDomain loAppDomain =
AppDomain.CreateDomain("CompilerDomain", null, loSetup);
//get the compiler to use based on the language
string className = null;
switch(m_language)
{
case ExpressionLanguage.CSharp:
className = "CSharpExpressionCompiler";
break;
case ExpressionLanguage.VisualBasic:
className = "VisualBasicExpressionCompiler";
break;
case ExpressionLanguage.JScript:
className = "JScriptExpressionCompiler";
break;
}
//create an instance of a compiler
compiler=(IExpressionCompiler)loAppDomain.CreateInstanceFromAndUnwrap(
"ExpressionEval.ExpressionCompiler.dll",
"ExpressionEval.ExpressionCompiler." + className);
try
{
//compile the expression
methodState = compiler.CompileExpression(expression, typeof(C),
typeof(R));
}
catch (CompileException e)
{
//catch any compile errors and throw an overall exception
StringBuilder exceptionMessage = new StringBuilder();
foreach (CompilerError error in e.CompileErrors)
{
exceptionMessage.Append("Error# ").Append(error.ErrorNumber);
exceptionMessage.Append(", column ").Append(error.Column);
exceptionMessage.Append(", ").Append(error.ErrorText);
exceptionMessage.Append(Environment.NewLine);
}
throw new ApplicationException(exceptionMessage.ToString());
}
finally
{
//unload the AppDomain
AppDomain.Unload(loAppDomain);
}
//if for some reason the code byte were not sent then return null
if (methodState != null && methodState.CodeBytes == null)
{
methodState = null;
}
return methodState;
}
/// <summary>
/// Compiles a DynamicMethodState and returns a delegate.
/// </summary>
/// <typeparam name="R">The return type of the
/// expression</typeparam>
/// <typeparam name="C">The type of the function
/// class</typeparam>
/// <param name="methodState">The serialized version of a
/// method on the functionClass</param>
/// <returns>ExecuteExpression<R, C> - a delegate that calls
/// the compiled expression</returns>
public ExecuteExpression<R, C> CreateExpressionDelegate<R,
C>(DynamicMethodState methodState)
{
ExecuteExpression<R, C> expressionDelegate;
//create a dynamic method
DynamicMethod dynamicMethod = new DynamicMethod(
"_" + Guid.NewGuid().ToString("N"), typeof(R),
new Type[] { typeof(C) }, typeof(C));
//get the IL writer for it
DynamicILInfo dynamicInfo = dynamicMethod.GetDynamicILInfo();
//set the properties gathered from the compiled expression
dynamicMethod.InitLocals = methodState.InitLocals;
//set local variables
SignatureHelper locals = SignatureHelper.GetLocalVarSigHelper();
foreach (int localIndex in methodState.LocalVariables.Keys)
{
LocalVariable localVar = methodState.LocalVariables[localIndex];
locals.AddArgument(Type.GetTypeFromHandle(localVar.LocalType),
localVar.IsPinned);
}
dynamicInfo.SetLocalSignature(locals.GetSignature());
//resolve any metadata tokens
IlTokenResolver tokenResolver = new IlTokenResolver(
methodState.TokenOffset.Fields,
methodState.TokenOffset.Methods,
methodState.TokenOffset.Types,
methodState.TokenOffset.LiteralStrings);
methodState.CodeBytes = tokenResolver.ResolveCodeTokens(
methodState.CodeBytes, dynamicInfo);
//set the IL code for the dynamic method
dynamicInfo.SetCode(methodState.CodeBytes, methodState.MaxStackSize);
//create a delegate for fast execution
expressionDelegate = (ExecuteExpression<R,
C>)dynamicMethod.CreateDelegate(
typeof(ExecuteExpression<R, C>));
return expressionDelegate;
}
}
The 'magic' for PossibilitiesThere appear to be quite a few possibilities from here for implementation enhancements or changes. Different .NET languages, a language translator from custom language to .NET language, supporting multiple code lines instead of a one line expression. Maybe being even more ambitious and creating a parser/IL generator to skip the CodeDom step and write IL directly. FeedbackI would appreciate any feedback you can give me on the code, concept, or the article itself. Also, I'm curious about your ideas for enhancements and if you implement this concept what was the result. History
|
||||||||||||||||||||||