动态 where 运算符 c#

Dynamic where operator c#

这个question12年前就有人问过,现在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 操作?

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

你每天都能学到新东西。如果您真的想使用尽可能少的字符串和代码(并且性能无关紧要),您可以使用 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 以来,这已经成为可能,但不能直接使用字符串。但是你可以写一个函数来实现这个。对于您的情况,您将需要一个用于操作员的开关盒 - 缓存委托可能有意义。

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

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

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

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

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

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

要遵守 RuleStatement 对象指定的 规则 Foo 元素必须匹配 Path 属性 RuleStatement 和比较,应用于 Value 属性 并基于 Operator 属性,必须 return 是肯定的结果。

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


一个可能的解决方案是将 Operator 属性 值映射到一个 Func 委托,该委托根据运算符执行比较,可能还有一组特定于每个操作员。

词典通常用于此类任务。
在一个简单的形式中,它可以是 Dictionary<string, Func<int, int, bool>>,因为比较了两个 int 值并且预期结果是 true/false

如果 Foo 和 RuleStatement class 可以 modified/adapted,则实现也可以更 通用 。字典映射器也可以是 RuleStatement class.

的一部分

例如,使 Foo class 实现可在类似情况下使用的接口:

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 class 可以更改为:

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<T> class.
如果将来可能需要更复杂的comparisons/operations,这种实施方式会提供更多灵活性


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

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