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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/714799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:13:22  来源:igfitidea点击:

Is there an easy way to parse a (lambda expression) string into an Action delegate?

c#.netlinqparsingexpression-trees

提问by Whisk

I have a method that alters an "Account" object based on the action delegate passed into it:

我有一个方法可以根据传递给它的动作委托来更改“帐户”对象:

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

This works as intended...

这按预期工作......

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

...but now what I'd like to try and do is have a method like this:

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

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();
}

It can then be used like:

然后可以像这样使用它:

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

to disable account "Account1234".

禁用帐户“Account1234”。

I've had a look at the linq dynamic query library, which seems to do more or less what I want but for Func type delegates, and my knowledge of Expression trees etc isn't quite good enough to work out how to achieve what I want.

我看过linq 动态查询库,它似乎或多或少地做了我想要的,但对于 Func 类型的委托,我对表达式树等的了解还不够好,无法弄清楚如何实现我想要的想。

Is there an easy way to do what I want, or do I need to learn expressions properly and write a load of code?

有没有一种简单的方法可以做我想做的事,或者我是否需要正确学习表达式并编写大量代码?

(The reason I want to do this is to allow an easy way of bulk updating account objects from a powershell script where the user can specify a lambda expression to perform the changes.)

(我想这样做的原因是允许从 PowerShell 脚本批量更新帐户对象的简单方法,用户可以在其中指定 lambda 表达式来执行更改。)

回答by RossFabricant

There is no general way to parse a string into a lambda expression without a full compilation, because lambda expressions can reference things that are defined outside the lambda expression. I know of no library that handles the specific case you want. There's a long discussion of this on a threadon a C# discussion group.

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

The easiest way to get what you want is to compile a method at runtime. You can write a function that takes in the string "a.Enabled = true; return a;" and sticks that in the middle of a function that takes an Account as a parameter. I would use this libraryas a starting point, but you can also use the function mentioned on another thread.

获得所需内容的最简单方法是在运行时编译方法。您可以编写一个接受字符串 "a.Enabled = true; return a;" 的函数 并将其粘贴在将 Account 作为参数的函数的中间。我会使用这个作为起点,但您也可以使用另一个线程中提到的函数。

回答by MichaelGG

The Dynamic LINQ library is a fine choice, as it'll generate expressions you can compile to code in a lightweight fashion.

动态 LINQ 库是一个不错的选择,因为它会生成表达式,您可以以轻量级的方式编译为代码。

The example you provided actually produces a boolean -- so you should be able to ask for a Func and it might sort it out.

您提供的示例实际上生成了一个布尔值——所以您应该能够要求一个 Func 并且它可能会解决它。

Edit: This of course is wrong, as Expressions don't have assignment in them at all.

编辑:这当然是错误的,因为表达式中根本没有赋值。

So, another potential way is to take two lambdas. One to find the property you want, one to provide a value:

因此,另一种可能的方法是采用两个 lambda。一个是找到你想要的属性,一个是提供一个值:

(a => a.AccountId), (a => true)

(a => a.AccountId), (a => true)

Then use reflection to set the property referenced in the first lambda with the result of the second one. Hackish, but it's still probably lightweight compared to invoking the C# compiler.

然后使用反射将第一个 lambda 中引用的属性设置为第二个的结果。Hackish,但与调用 C# 编译器相比,它仍然可能是轻量级的。

This way you don't have to do much codegen yourself - the expressions you get will contain most everything you need.

这样你就不必自己做太多的代码生成——你得到的表达式将包含你需要的大部分内容。

回答by Alex Yakunin

That's easy:

这很容易:

  • Use CodeDomto generate the module containing the "surrounding class" you'll use to build the expression; this class must implement the interface known to your application
  • Use CodeSnippedExpressionto inject the expression into its member.
  • Use Activatortype to create the instance of this class in runtime.
  • 使用CodeDom生成包含您将用于构建表达式的“周围类”的模块;此类必须实现您的应用程序已知的接口
  • 使用CodeSnippedExpression将表达式注入到其成员中。
  • 使用Activator类型在运行时创建此类的实例。

Basically, you need to build the following class with CodeDom:

基本上,您需要使用 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];
      }
    } 
  }
}

Assuming that IHasAccountActionis:

假设IHasAccountAction是:

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

If this is done, you can get the expression compiled from string with ease. If you need its expression tree representation, use Expression<Action<Account>>instead of Action<Account>in generated type.

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

回答by jpbochi

You may try this: Dynamic Lambda Expressions Using An Isolated AppDomain

你可以试试这个:Dynamic Lambda Expressions Using An Independent AppDomain

It compiles a lambda expression using CodeDOM compiler. In order to dispose the in-memory assembly that gets created, the compiler runs on an isolated AppDomain. For the passing the expression through the domain boundary, it has to be serialized. Alas, Expression<>is not Serializable. So, a trick has to be used. All the details are explained in the post.

它使用 CodeDOM 编译器编译 lambda 表达式。为了处理创建的内存中程序集,编译器在隔离的AppDomain. 为了通过域边界传递表达式,它必须被序列化。唉,Expression<>不是Serializable。所以,必须使用一个技巧。所有细节都在帖子中解释。

I'm the author of that component, by the way. I would like very much to hear your feedback from it.

顺便说一下,我是那个组件的作者。我非常希望听到您的反馈。