我想在C#中执行动态创建的字符串.我知道VB和JScript.Net可以做到这一点,甚至还有一种方法可以在C#中使用它的程序集作为解决方法.我还发现这篇文章描述了如何做到这一点.
我今天读到了有关C#4.0功能的内容,这些功能使它更接近动态语言,而这些语言已成为主要功能之一.那么,是否有人知道C#4.0是否包含一些允许字符串执行的内置功能,或者其他任何方式来执行上面文章中描述的内容.
这很容易做到.我构建了以下便利包装器.它们的结构使您可以从定义方法或表达式的源代码片段构造程序集,并使用DynamicCodeManager的辅助方法通过名称调用它们.
代码是根据需要编译的,以响应调用.添加更多方法将导致下次调用时自动重新编译.
您只提供方法体.如果您不想返回值,则返回null,并且不必费心使用InvokeMethod返回的对象.
如果你在商业代码中使用它,帮我一个忙,并相信我的工作.这个库中真正的宝石是调用支持.获取编译代码不是问题,而是调用.当你有一个可变长度参数列表时,让反射正确匹配方法签名是非常棘手的.这就是DynamicBase存在的原因:编译器解析了与这个显式声明的基类的方法绑定,使我们能够访问正确的VMT.从那里开始,所有这些都在洗涤中出现.
我还应该指出,此功能使您的桌面应用程序容易受到脚本注入攻击.您应该非常小心地审查脚本的来源或降低生成的程序集运行的信任级别.
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); } } }
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_conditionSnippet = new Dictionary (); static Dictionary _methodSnippet = new Dictionary (); static string CodeStart = "using System;\r\nusing System.Collections.Generic;\r\n//using System.Linq;\r\nusing System.Text;\r\nusing System.Data;\r\nusing System.Reflection;\r\nusing System.CodeDom.Compiler;\r\nusing Microsoft.CSharp;\r\nnamespace Dynamo\r\n{\r\n public class Dynamic : DynamicBase\r\n {\r\n"; static string DynamicConditionPrefix = "__dm_"; static string ConditionTemplate = " bool {0}{1}(params object[] p) {{ return {2}; }}\r\n"; static string MethodTemplate = " object {0}(params object[] p) {{\r\n{1}\r\n }}\r\n"; static string CodeEnd = " }\r\n}"; static List _references = new List ("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\r\n{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 kvp in _conditionSnippet) src.AppendFormat(ConditionTemplate, DynamicConditionPrefix, kvp.Key, kvp.Value); foreach (KeyValuePair kvp in _methodSnippet) src.AppendFormat(MethodTemplate, kvp.Key, kvp.Value); src.Append(CodeEnd); Trace.TraceError("SOURCE\r\n{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); } } }
除了将其编译成程序集然后执行它之外,没有其他方法可以执行任意C#源代码.Anders Hejlsberg(C#的架构师)宣布计划将C#编译器作为服务(基本上是一组CLR类)公开,所以当发生这种情况时,这可能会有所帮助.
"编译器即服务"基本上意味着您可以将任意一段代码编译到Expression中,或者更好地编译到AST中,并且通常可以获得内部编译器工作.