【问题标题】:Parse string to C# lambda Func将字符串解析为 C# lambda Func
【发布时间】:2009-11-10 13:09:45
【问题描述】:

有没有办法将 lambda 的字符串表示形式转换为 lambda Func?

Func<Product, bool> func = Parse<Product, bool>("product => product.Name.Length > 0");

我尝试了 Dynamic LINQ,但它没有按预期工作 - 例如它不需要 lambda 语法 =>。

答案总结:

  • 编写我自己的 C# 编译器 - 非常有趣
  • 启动外部编译器(如 csc.exe) - 非常慢
  • 使用 LINQ - 正如我所说,我看不出它如何解析 lambda 表达式

我为什么需要这个:因为没有办法将 lambdas 传递给自定义属性,例如

[Secure(role => role.CanDoThis && role.AllowedCount > 5)]

因此,作为一种解决方法,我想将 lambda 作为字符串传递:“role => role.CanDoThis && role.AllowedCount > 5”。但似乎我必须像这样使用 LINQ:“CanDoThis && AllowedCount > 5” - 因为这是它理解的语法。但我的问题是关于真正的 lambda,我在提问时已经使用过 LINQ。

【问题讨论】:

  • 您为什么担心启动编译器会很慢?您可以缓存结果表达式。
  • 似乎 C# 5 会提供一些东西来做你想做的事。观看来自 PDC 2008 的视频,其中 Anders Hejlsberg 谈到了 C# 的未来。
  • 我正在等待 C# 4.0 发布... C# 5 太遥远了 ;-) 我实际上需要这个特性来用于属性中的 lambda。希望 4.0 将拥有它(以及通用属性)。
  • c# 4.0 中不会有通用属性。 stackoverflow.com/questions/1211170/…
  • 虽然很遗憾,但在这种情况下,我需要能够将 lambdas/delegates 传递给属性,而不是通用属性。引用问题中的 4.0 规范让我害怕 ;-) 它甚至不包含“lambda”这个词!

标签: c# linq


【解决方案1】:

它们是许多可用的 lambda 表达式解析器。其中一些是Lambda-ParserSprache

示例代码:

Example1 : string concat and number calculate:

string code = "2.ToString()+(4*2)"; // C# code Func<string> 
func = ExpressionParser.Compile<Func<string>>(code); // compile code 
string result = func(); // result = "28"

【讨论】:

    【解决方案2】:

    您可以解析字符串并使用Expression 类构建一个 lambda 表达式,基本上复制了编译器的功能。

    【讨论】:

    • 我想花时间在上面可能会很有趣,但对我的客户来说却不是——他们不会付钱给我写 C# 编译器。
    • 多么奇怪的客户。 :)
    【解决方案3】:

    我猜你必须求助于 CSharpCodeProvider。但是,处理所有可能的局部变量引用可能并非易事。你将如何告诉 CSharpCodeProvider lambda 参数的类型?我可能会创建一个如下所示的模板类:

    class ExpressionContainer {
        public Expression<Func<Product, bool>> TheExpression;
        public string Length;
    
        public ExpressionContainer() {
            TheExpression = <user expression text>;
        }
    }
    

    然后做这样的事情:

    string source = <Code from above>;
    Assembly a;
    using (CSharpCodeProvider provider = new CSharpCodeProvider(...) {
        List<string> assemblies = new List<string>();
        foreach (Assembly x in AppDomain.CurrentDomain.GetAssemblies()) {
            try {
                assemblies.Add(x.Location);
            }
            catch (NotSupportedException) {
                // Dynamic assemblies will throw, and in .net 3.5 there seems to be no way of finding out whether the assembly is dynamic before trying.
            }
        }
    
        CompilerResults r = provider.CompileAssemblyFromSource(new CompilerParameters(assemblies.ToArray()) { GenerateExecutable = false, GenerateInMemory = true }, source);
        if (r.Errors.HasErrors)
            throw new Exception("Errors compiling expression: " + string.Join(Environment.NewLine, r.Errors.OfType<CompilerError>().Select(e => e.ErrorText).ToArray()));
        a = r.CompiledAssembly;
    }
    object o = a.CreateInstance("ExpressionContainer");
    var result = ( Expression<Func<Product, bool>>)o.GetType().GetProperty("TheExpression").GetValue(o);
    

    但是请注意,对于长时间运行的应用程序,您应该在单独的 appdomain 中创建所有这些内存中的程序集,因为在卸载它们所在的 appdomain 之前无法释放它们。

    【讨论】:

    • 是的,然后我将运行 csc.exe,AppDomains 仅用于几个 lambdas...这让我想起了我的 Turbo Pascal 程序,它允许用户输入表达式...并且必须与 Turbo Pascal 编译器一起部署 ;-)
    • 另一方面 - 有一种叫做“动态方法”的东西。一种更轻量级的方式来处理此类情况。不幸的是 - 我从未使用过它们。 ://
    • @queen:这有关系吗?您的客户已经部署了 csc,因为它是框架的一部分。此外,在单独的应用程序域中运行的全部目的是您可以检索该值,然后拆除应用程序域。您的客户可以购买相当多的新服务器来处理计算,而您无需自己实现编译器即可节省资金。
    【解决方案4】:

    您也许可以使用CSharpCodeProvider 做一些事情(用更多代码包装表达式以创建一个有效的类并将其编译成一个程序集,然后加载该程序集)。

    我相信LINQPad 就是这样做的。

    【讨论】:

    • 我知道如何调用 csc.exe,并且有使用 CodeDom 进行动态编译的经验,这是太多的开销 - 根据我的经验,它实际上运行 csc.exe(当我使用它时)在 .NET 1.1 上)。
    【解决方案5】:

    针对您更具体的问题(您可能已经知道这一点,但我还是会尝试提及),您可以创建一个字典,将可以是常量(整数或枚举)的值映射到 lambda。

    sealed class Product {
       public bool CanDoThis { get; set; }
       public int AllowedCount { get; set; }
    }
    
    public enum SecureFuncType {
       Type1,
       Type2,
       Type3
    }
    
    sealed class SecureAttribute : Attribute {
       [NotNull] readonly Func<Product, bool> mFunc;
    
       public SecureAttribute(SecureFuncType pType) {
          var secureFuncs = new Dictionary<SecureFuncType, Func<Product, bool>> {
             { SecureFuncType.Type1, role => role.CanDoThis && role.AllowedCount > 1 },
             { SecureFuncType.Type2, role => role.CanDoThis && role.AllowedCount > 2 },
             { SecureFuncType.Type3, role => role.CanDoThis && role.AllowedCount > 3 }
          };
    
          mFunc = secureFuncs[pType];
       }
    }
    

    [Secure(SecureFuncType.Type1)]
    sealed class TestClass {
    }
    
    // etc...
    

    【讨论】:

      猜你喜欢
      • 2010-12-08
      • 2011-11-26
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      • 2012-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多