This is dead easy to do. I built the following convenience wrappers. They are structured so that you can construct an assembly from fragments of source code defining methods or expressions and invoke them by name using the helper methods of DynamicCodeManager.
The code is compiled on demand in response to invocation. Adding more methods will cause automatic recompilation on next invocation.
You provide only a method body. If you don't want to return a value then return null and don't bother to use the object returned by InvokeMethod.
If you use this in commercial code do me a favour and credit my work. The real jewel in this library is the invocation support. Getting the code to compile isn't the problem, it's invocation. It's quite tricky to get reflection to correctly match the method signature when you have a variable length parameter list. This is the reason for the existence of DynamicBase: the compiler resolves method binding to this explicitly declared base class giving us access to the right VMT. From there on in it all comes out in the wash.
I should also point out that this capability makes your desktop application vulnerable to script injection attacks. You should either take great care to vet the origin of script or reduce the trust level under which the generated assembly runs.
DynamicBase.cs
using System.Reflection;
namespace Dynamo
{
public abstract class DynamicBase
{
public bool EvaluateCondition(string methodName, params object[] p)
{
methodName = string.Format("__dm_{0}", methodName);
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
return (bool)GetType().InvokeMember(methodName, flags, null, this, p);
}
public object InvokeMethod(string methodName, params object[] p)
{
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
return GetType().InvokeMember(methodName, flags, null, this, p);
}
public double Transform(string functionName, params object[] p)
{
functionName = string.Format("__dm_{0}", functionName);
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
return (double)GetType().InvokeMember(functionName, flags, null, this, p);
}
}
}
DynamicCodeManager.cs
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;
namespace Dynamo
{
public static class DynamicCodeManager
{
#region internal statics and constants
static Dictionary<string, string> _conditionSnippet = new Dictionary<string, string>();
static Dictionary<string, string> _methodSnippet = new Dictionary<string, string>();
static string CodeStart = "using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
namespace Dynamo
{
public class Dynamic : DynamicBase
{
";
static string DynamicConditionPrefix = "__dm_";
static string ConditionTemplate = " bool {0}{1}(params object[] p) {{ return {2}; }}
";
static string MethodTemplate = " object {0}(params object[] p) {{
{1}
}}
";
static string CodeEnd = " }
}";
static List<string> _references = new List<string>("System.dll,System.dll,System.Data.dll,System.Xml.dll,mscorlib.dll,System.Windows.Forms.dll".Split(new char[] { ',' }));
static Assembly _assembly = null;
#endregion
public static Assembly Assembly { get { return DynamicCodeManager._assembly; } }
#region manage snippets
public static void Clear()
{
_methodSnippet.Clear();
_conditionSnippet.Clear();
_assembly = null;
}
public static void Clear(string name)
{
if (_conditionSnippet.ContainsKey(name))
{
_assembly = null;
_conditionSnippet.Remove(name);
}
else if (_methodSnippet.ContainsKey(name))
{
_assembly = null;
_methodSnippet.Remove(name);
}
}
public static void AddCondition(string conditionName, string booleanExpression)
{
if (_conditionSnippet.ContainsKey(conditionName))
throw new InvalidOperationException(string.Format("There is already a condition called '{0}'", conditionName));
StringBuilder src = new StringBuilder(CodeStart);
src.AppendFormat(ConditionTemplate, DynamicConditionPrefix, conditionName, booleanExpression);
src.Append(CodeEnd);
Compile(src.ToString()); //if the condition is invalid an exception will occur here
_conditionSnippet[conditionName] = booleanExpression;
_assembly = null;
}
public static void AddMethod(string methodName, string methodSource)
{
if (_methodSnippet.ContainsKey(methodName))
throw new InvalidOperationException(string.Format("There is already a method called '{0}'", methodName));
if (methodName.StartsWith(DynamicConditionPrefix))
throw new InvalidOperationException(string.Format("'{0}' is not a valid method name because the '{1}' prefix is reserved for internal use with conditions", methodName, DynamicConditionPrefix));
StringBuilder src = new StringBuilder(CodeStart);
src.AppendFormat(MethodTemplate, methodName, methodSource);
src.Append(CodeEnd);
Trace.TraceError("SOURCE
{0}", src);
Compile(src.ToString()); //if the condition is invalid an exception will occur here
_methodSnippet[methodName] = methodSource;
_assembly = null;
}
#endregion
#region use snippets
public static object InvokeMethod(string methodName, params object[] p)
{
DynamicBase _dynamicMethod = null;
if (_assembly == null)
{
Compile();
_dynamicMethod = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
}
return _dynamicMethod.InvokeMethod(methodName, p);
}
public static bool Evaluate(string conditionName, params object[] p)
{
DynamicBase _dynamicCondition = null;
if (_assembly == null)
{
Compile();
_dynamicCondition = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
}
return _dynamicCondition.EvaluateCondition(conditionName, p);
}
public static double Transform(string functionName, params object[] p)
{
DynamicBase _dynamicCondition = null;
if (_assembly == null)
{
Compile();
_dynamicCondition = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
}
return _dynamicCondition.Transform(functionName, p);
}
#endregion
#region support routines
public static string ProduceConditionName(Guid conditionId)
{
StringBuilder cn = new StringBuilder();
foreach (char c in conditionId.ToString().ToCharArray()) if (char.IsLetterOrDigit(c)) cn.Append(c);
string conditionName = cn.ToString();
return string.Format("_dm_{0}",cn);
}
private static void Compile()
{
if (_assembly == null)
{
StringBuilder src = new StringBuilder(CodeStart);
foreach (KeyValuePair<string, string> kvp in _conditionSnippet)
src.AppendFormat(ConditionTemplate, DynamicConditionPrefix, kvp.Key, kvp.Value);
foreach (KeyValuePair<string, string> kvp in _methodSnippet)
src.AppendFormat(MethodTemplate, kvp.Key, kvp.Value);
src.Append(CodeEnd);
Trace.TraceError("SOURCE
{0}", src);
_assembly = Compile(src.ToString());
}
}
private static Assembly Compile(string sourceCode)
{
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.AddRange(_references.ToArray());
cp.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName);
cp.CompilerOptions = "/target:library /optimize";
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
CompilerResults cr = (new CSharpCodeProvider()).CompileAssemblyFromSource(cp, sourceCode);
if (cr.Errors.Count > 0) throw new CompilerException(cr.Errors);
return cr.CompiledAssembly;
}
#endregion
public static bool HasItem(string methodName)
{
return _conditionSnippet.ContainsKey(methodName) || _methodSnippet.ContainsKey(methodName);
}
}
}