【问题标题】:Dynamic where operator c#动态 where 运算符 c#
【发布时间】:2021-10-06 07:24:29
【问题描述】:

这个question是12年前问的,但是dotnet变化这么大,不知道现在有没有解决办法。

我有一个模型,代表我要执行的规则和运算符:

public class RuleStatement
{
    public string Path { get; set; }
    public string Operator { get; set; }
    public int Value { get; set; }
}

来源:

public class Foo
{
   public string Path {get;set;}
   public int Value {get;set;}
}

我想在源上执行这个动态的where 操作符,所以只有符合所有规则的才会返回。示例:

List<Foo> source = new List<Foo>();
//Sample of source
source.Add(new Foo{Path="A", Value = 10});
source.Add(new Foo{Path="B", Value = 20});
source.Add(new Foo{Path="C", Value = 30});

//Scenario 1 => Expected to be true
List<RuleStatement> rules = new List<RuleStatement>();
rules.Add(new RuleStatement{Path="A", Operator = ">=", Value=10});
rules.Add(new RuleStatement{Path="B", Operator = "==", Value=20});

bool isMatch = rules.All(rule => 
   source.Any(s => s.Path == rule.Path && s.Value $"{rule.Operator}" rule.Value));

//Scenario 2 => Expected to be false
List<RuleStatement> rules = new List<RuleStatement>();
rules.Add(new RuleStatement{Path="A", Operator = ">=", Value=100});
rules.Add(new RuleStatement{Path="B", Operator = "==", Value=20});

bool isMatch = rules.All(rule => 
   source.Any(s => s.Path == rule.Path && s.Value $"{rule.Operator}" rule.Value));

我找不到任何可行的解决方案,那么是否可以在 .NET Core 5 上执行此动态 where 操作?

如果不可能,有什么办法可以解决这个问题?

【问题讨论】:

  • 您可以为 Value 和 Operator 添加一些可能的值吗?你预计会发生什么?
  • 因为 CSharp 是静态类型的并且是编译时语言,所以即使在 .NET 5.0 中也不支持基于字符串的运算符。您链接的帖子有适合这种语言限制的解决方法。
  • 您听说过Expression&lt;Func&lt;T,bool&gt;&gt;,其中T 是您的班级。是的,您可以使用泛型和 Expression 类来做到这一点。
  • @WouterdeKort 我添加了一个更好的可能值示例。
  • 我将使用Dictionary&lt;string, Func&lt;string, RuleStatement, bool&gt;&gt;,其中T1=源值,T2=包含type的规则,用于转换和比较值。

标签: c# linq .net-core dynamic


【解决方案1】:

你每天都会学到新东西。如果你真的想尽可能少地使用字符串和代码(性能并不重要),你可以使用 NuGet 包Microsoft.CodeAnalysis.Scripting

using Microsoft.CodeAnalysis.CSharp.Scripting;

s.Path == rule.Path && CSharpScript.EvaluateAsync<bool>($"{s.Value} {rule.Operator} {rule.Value}").Result;

如果性能更重要,那么也许这就是要走的路:

从 .Net Framework 3.5 开始,这已经成为可能,但不能直接使用字符串。但是你可以编写一个函数来实现这一点。对于您的情况,您将需要一个用于操作员的 switch case - 也许缓存代表是有意义的。

using System.Linq.Expressions;

var param1 = Expression.Parameter(typeof(int));
var param2 = Expression.Parameter(typeof(int));
var expr = Expression.LessThan(param1, param2);

var func = Expression.Lambda<Func<int, int, bool>>(expr, param1, param2).Compile();

var result = func(1,2);

【讨论】:

  • 太棒了!谢谢哥们。我认为这里的性能有点关键,因为它是一个事件驱动的应用程序,我需要对每个事件进行评估,平均每秒大约 5k 个事件。但我也可以扩展微服务,因此我将测试这两种解决方案。
【解决方案2】:

问题:
类元素的集合,定义为:

List<Foo> source = new List<Foo>();

public class Foo
{
   public string Path {get;set;}
   public int Value {get;set;}
}

需要与另一个类对象中定义的一组规则进行比较,定义为:

public class RuleStatement
{
    public string Path { get; set; }
    public string Operator { get; set; }
    public int Value { get; set; }
}

要遵守RuleStatement 对象指定的规则Foo 元素必须与RuleStatementPath 属性匹配,并且比较应用于Value属性并基于Operator 属性,必须返回肯定的结果。

Operator 属性被定义为一个字符串,这使得基于隐式/显式运算符的实现有些复杂。
Foo 元素是否符合指定规则的测试也是时间敏感的。


一种可能的解决方案是将Operator 属性值映射到Func 委托,该委托根据运算符以及可能特定于每个运算符的一组其他条件执行比较。

字典通常用于此类任务。
在简单的形式中,它可能是 Dictionary&lt;string, Func&lt;int, int, bool&gt;&gt;,因为比较了两个 int 值并预期得到 true/false 结果。

如果 Foo 和 RuleStatement 类可以修改/调整,则实现也可以更通用。 Dictionary 映射器也可以是RuleStatement 类的一部分。

例如,让 Foo 类实现一个可以在类似情况下使用的接口:

public interface IGenericFoos
{
    string Path { get; set; }
    int Value { get; set; }
}

public class Foo : IGenericFoos
{
    public string Path { get; set; }
    public int Value { get; set; }
}

RuleStatement 类可以更改为:

public class RuleStatement<T> where T : IGenericFoos
{
    public static Dictionary<string, Func<T, RuleStatement<T>, bool>> operators =
        new Dictionary<string, Func<T, RuleStatement<T>, bool>>() {
            [">="] = (T, R) => T.Value >= R.Value,
            ["<="] = (T, R) => T.Value <= R.Value,
            ["<>"] = (T, R) => T.Value != R.Value,
            ["!="] = (T, R) => T.Value != R.Value,
            ["=="] = (T, R) => T.Value == R.Value,
            ["="] = (T, R) => T.Value == R.Value,
            ["<"] = (T, R) => T.Value < R.Value,
            [">"] = (T, R) => T.Value > R.Value,
        };

    public string Path { get; set; }
    public string Operator { get; set; }
    public int Value { get; set; }

    public bool Eval(T ifoo) => ifoo.Path == Path && operators[Operator](ifoo, this);
}

评估所有 Foo 对象是否符合一组规则的 LINQ 查询可以简化为:

var source = new List<Foo> { ... }
var rules = new List<RuleStatement<Foo>> { ... }
// [...]
bool isMatch = rules.All(rule => source.Any(s => rule.Eval(s)));

// Or implicitly:
bool isMatch = rules.All(rule => source.Any(rule.Eval));

可以添加其他类型的运算符来执行不同的比较或其他操作。例如,“+”和“-”运算符可以使用固定值进行评估,例如:

["+"] = (T, R) => T.Value + R.Value >= 0,
["-"] = (T, R) => T.Value - R.Value >= 0,

或使用变量值,将一个属性(最终也是特定的重载构造函数)添加到RuleStatement&lt;T&gt; 类。
如果将来可能需要更复杂的比较/操作,这种实现会提供更多灵活性


如果无法修改这些类,则可以将 Dictionary 用作独立字段(或任何适合的字段),并且可以在以下位置更改 LINQ 查询(保持 OP 中显示的原始定义):

bool isMatch = rules.All(rule => source
    .Any(s => s.Path == rule.Path && operators[rule.Operator](s.Value, rule.Value)));

【讨论】:

    猜你喜欢
    • 2017-08-22
    • 1970-01-01
    • 2010-11-15
    • 1970-01-01
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多