有没有一种简单的方法将lambda表达式字符串解析为Action委托?

15
我有一个方法,根据传递的操作委托修改一个“帐户”对象:
public static void AlterAccount(string AccountID, Action<Account> AccountAction) {
  Account someAccount = accountRepository.GetAccount(AccountID);
  AccountAction.Invoke(someAccount);
  someAccount.Save();
}

这个按预期工作了...

AlterAccount("Account1234", a => a.Enabled = false);

... 但是现在我想尝试的是有一个像这样的方法:

public static void AlterAccount(string AccountID, string AccountActionText) {
  Account someAccount = accountRepository.GetAccount(AccountID);
  Action<Account> AccountAction = MagicLibrary.ConvertMagically<Action<Account>>(AccountActionText);
  AccountAction.Invoke(someAccount);
  someAccount.Save();
}

然后可以像这样使用:

AlterAccount("Account1234", "a => a.Enabled = false");

禁用名为 "Account1234" 的账户。

我查看了linq动态查询库,它似乎可以做到我想要的,但只适用于Func类型委托,而且我的表达式树等方面的知识还不够好,无法找出如何实现我想要的功能。

有没有一种简单的方法来达到我想要的效果,或者我需要学习表达式并编写大量的代码?

(我之所以想这样做,是为了允许用户从PowerShell脚本轻松地批量更新账户对象,其中用户可以指定Lambda表达式来执行更改。)


用System.Reflection.Emit.DynamicMethod并生成IL代码是一种较为困难的方法。然而,在这种情况下,更容易的方法可能是接受指定属性名称和值的语法,并具有反射基础的属性设置器。 - eulerfx
是的,我考虑过这个问题,但我认为这样做可能会排除一些潜在的强大功能,比如能够调用AlterAccount("Account1234", "a=>a.Enabled = !a.Enabled");(不确定为什么你想特别这样做,但还有其他更合理的例子)。 - Whisk
3
我读到这句话时忍不住大笑了起来:MagicLibrary.ConvertMagically - The Muffin Man
4个回答

4
动态LINQ库是一个不错的选择,因为它可以生成表达式,您可以以轻量级的方式编译成代码。
您提供的示例实际上产生了一个布尔值,因此您应该能够要求一个Func并解决它。
编辑:当然,这是错误的,因为表达式根本没有赋值。
因此,另一种潜在的方法是采用两个lambda。一个用于查找所需的属性,另一个用于提供值:
(a => a.AccountId),(a => true)
然后使用反射设置第一个lambda中引用的属性与第二个lambda的结果。虽然有点hackish,但与调用C#编译器相比仍然可能是轻量级的。
这样,您就不必自己进行太多的代码生成-您获得的表达式将包含大部分您需要的内容。

我认为...如果我使用"a => a.Enabled == false",那么它会起作用,但是我正在使用"a => a.Enabled = false" - 我想在账户上执行该操作,因此我认为它需要进行一些修改才能正常工作。 - Whisk
你说得对。它适用于函数:Func<bool> exp = () => test = false; 但不适用于表达式,因为它们没有表示赋值的方法。 - MichaelGG
是的 - 我想我可能会尝试修改动态LINQ的东西,使其能够与Action<T>以及Func<T,TResult>一起使用... - Whisk
Whisk - 我们对动态LINQ进行了大量修改(添加了let和do绑定,以及其他各种功能)。它编译成表达式,而表达式没有赋值。 - MichaelGG
所以,基本上你需要大幅度地扩展它。例如,按 = 分割,编译每个表达式,然后使用代码生成关联实际的赋值/设置属性。 - MichaelGG
是的,我开始朝那个方向努力,但我认为这可能比为此付出的努力更加困难-看起来.NET 4.0将拥有一个Expression.AssignField方法,这可能会有所帮助...我会尝试您的2个lambda想法并看看进展如何... - Whisk

3
你可以尝试这个:使用隔离的AppDomain动态Lambda表达式 它使用CodeDOM编译器编译Lambda表达式。为了处理在内存中创建的程序集的释放,编译器在一个隔离的AppDomain上运行。为了将表达式通过域边界传递,它必须进行序列化。遗憾的是,Expression<>不可序列化。因此,必须使用一种技巧。所有细节都在文章中有详细解释。
顺便说一下,我是那个组件的作者。我很想听听你的反馈。

1

没有通用的方法可以将字符串解析为lambda表达式而不进行完整编译,因为lambda表达式可以引用在lambda表达式外定义的内容。我不知道有哪个库可以处理您想要的特定情况。在C#讨论组的线程上有一个长时间的讨论。

获取您想要的最简单的方法是在运行时编译方法。您可以编写一个函数,该函数接受字符串“a.Enabled = true; return a;”并将其插入以Account作为参数的函数中间。我会使用这个作为起点,但您也可以使用另一个线程中提到的函数。


谢谢 - 经过快速尝试,似乎无法将类似 "a => a.Enabled = true" 的字符串评估为 Action<Account> 委托 - 我做错了什么还是它不起作用,因为它本身不是完整的 C# 语句?我已经更新了问题以澄清。 - Whisk
rossfabricant; 他提到的动态LINQ库正是如此。当然,在编译时必须提供输入信息,但它可以很好地编译。 - MichaelGG
它可以构建查询,但我没有看到它能够构建任何修改状态的内容。 - RossFabricant
这很有趣 - 特别是在 microsoft.public.dotnet.languages.csharp 上的线程 - 看起来使用表达式树是前进的道路,虽然我不太明白动态编译路线如何允许在前面输入一个简单的 lambda 并获得所需的结果。 - Whisk
一个lambda表达式行不通,但是如果您预先知道所有参数的类型和数量,可以使用这种方法来消除所有样板代码,客户端代码只需传入类似于“a.Enabled =!a.Enabled”的字符串即可。 - RossFabricant
是的,它只能间接地修改状态。所以它可以这样做 => a.SetEnabled(true),对吧? - MichaelGG

0

这很容易:

  • 使用CodeDom生成包含您将用于构建表达式的"周围类"的模块; 此类必须实现应用程序已知的接口
  • 使用CodeSnippedExpression将表达式注入其成员。
  • 使用Activator类型在运行时创建此类的实例。

基本上,您需要使用 CodeDom 构建以下类:

using System;
using MyNamespace1;
using ...
using MyNamespace[N];

namespace MyNamespace.GeneratedTypes 
{
  public class ExpressionContainer[M] : IHasAccountAction
  {
    public Action<Account> AccountAction { 
      get {
        return [CodeSnippedExpression must be used here];
      }
    } 
  }
}

假设 IHasAccountAction 是:

public IHasAccountAction {
  public Action<Account> AccountAction { get; }
}

如果这样做,您可以轻松地从字符串编译表达式。如果您需要其表达式树表示,请在生成的类型中使用Expression<Action<Account>>而不是Action<Account>

(顺便回复了你在通用运算符上的评论) - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接