使用数字操作数的字符串值创建自定义谓词

本文关键字:创建 自定义 谓词 字符串 数字 操作数 | 更新日期: 2023-09-27 18:26:36

我正试图将数字操作数表达式的字符串值("GreaterThan"、"Equals"等)传递给参数。我已经创建了下面的代码,但它很"笨拙"。我不喜欢if块,我认为有一种方法可以通过自定义LINQ比较谓词来实现这一点。我试着按照这篇帖子中发布的回复,但我似乎无法按照。关于如何清理我的方法,有什么想法吗?

下面的代码显示了如何将字符串值"GreaterThan"传递给函数

    var myValues = new Dictionary<string, int> {{"Foo", 1}, {"Bar", 6}};
    var failed = DoAnyValuesFail(myValues, "GreaterThan", 4);

这是我写的"笨拙"的示例方法:

    public bool DoAnyValuesFail(Dictionary<string, int> dictionary, string expression, int failureValue)
    {
        var failureValues = new List<KeyValuePair<string, int>>();
        if (expression == "GreaterThan")
            failureValues = dictionary.Where(x => x.Value > failureValue).ToList();
        if (expression == "LessThan")
            failureValues = dictionary.Where(x => x.Value < failureValue).ToList();
        if (expression == "Equals")
            failureValues = dictionary.Where(x => x.Value == failureValue).ToList();
        return failureValues.Any();
    }

---更新-最终版本---

我认为以下回答中的部分困惑是,我对函数、谓词和委托的术语没有跟上。很抱歉。无论如何,我确实想澄清一件事,那就是"GreaterThan"、"LessThan"answers"Equals"的值来自配置文件,因此它们需要是在运行时调整的"魔术串"。

因此,根据Matthew Haugen和Enigmativity的反馈,我提出了以下代码,我认为最适合我的需求。如果你认为这是错误的或需要调整,我愿意接受任何建议。

// These values actually come from a configuration file... shown here as hard coded just for illustration purposes
var failureValue = 2;
var numericQualifier = "<";
// This comes from my external data source
var myValues = new Dictionary<string, int> { { "Foo", 1 }, { "Bar", 6 } };
// This is the delegate (am I using that term correctly?) called Compare which is setup as an extension method
var failureValues = myValues.Where(x => numericQualifier.Compare()(x.Value, failureValue)).ToList();
if (failureValues.Any())
    Console.WriteLine("The following values failed: {0}", string.Join(", ", failureValues));

这就是我的Compare扩展方法:

public static class MyExtensions
{
    public static Func<int, int, bool> Compare(this string expression)
    {
        switch (expression)
        {
            case "GreaterThan":
            case ">":
                return (v, f) => v > f;
            case "LessThan":
            case "<":
                return (v, f) => v < f;
            case "Equals":
            case "=":
                return (v, f) => v == f;
            default:
                throw new ArgumentException(string.Format("The expression of '{0}' is invalid.  Valid values are 'GreaterThan', 'LessThan' or 'Equals' or their respective symbols (>,<,=)", expression));
        }
    }
}

使用数字操作数的字符串值创建自定义谓词

如果您需要将表达式与字符串匹配,我倾向于这样做:

private Dictionary<string, Func<int, int, bool>> _predicates =
    new Dictionary<string, Func<int, int, bool>>
    {
        { "GreaterThan", (v, f) => v > f },
        { "LessThan", (v, f) => v < f },
        { "Equals", (v, f) => v == f },
    };
public bool DoAnyValuesFail(
    Dictionary<string, int> dictionary,
    string expression,
    int failureValue)
{
    return _predicates.ContainsKey(expression)
        ? dictionary.Any(kvp => _predicates[expression](kvp.Value, failureValue))
        : false;
}

然而,正如其他人所说,我认为这是一个更好的选择:

public bool DoAnyValuesFail(
    Dictionary<string, int> dictionary,
    Func<int, bool> predicate)
{
    return dictionary.Any(kvp => predicate(kvp.Value));
}

然后简单地这样称呼它:

var failed = DoAnyValuesFail(myValues, x => x > 4);

但你离让它变得更简单只有一步之遥:

var failed = myValues.Any(x => x.Value > 4);

不需要DoAnyValuesFail方法——这意味着代码更简单,潜在的错误更少,并且没有"神奇"的字符串。

这段代码比原来的代码更清晰、更简洁。

我会先将其设为enum,而不是`string。

public enum ComparisonType
{
    GreaterThan,
    LessThan,
    Equal,
}

然后,我会把它改成这样。这也将提高性能,因为只需要返回一个匹配的值。

public bool DoAnyValuesFail(Dictionary<string, int> dictionary, ComparisonType expression, int failureValue)
{
    switch (expression)
    {
        case ComparisonType.Equals:
            return dictionary.Any(x => x.Value == failureValue);
        case ComparisonType.GreaterThan:
            return dictionary.Any(x => x.Value > failureValue);
        case ComparisonType.LessThan:
            return dictionary.Any(x => x.Value < failureValue);
        default:
            throw new NotSupportedException();
    }
}

当然,它并不是所有都比你现有的干净得多。它可能比依赖那些string输入更可靠,这使它更有可读性。在我看来,不通过List<>有帮助。但我认为除此之外你没什么可做的。我的意思是,可以Func<T, bool>存储在switch中指定的值中,然后使用它,这将规范化return dictionary.Any(...),但我觉得这会降低它的可读性。

最终,我认为它是好的。你用Expression做的任何事情都会因为功能如此简单而失去可读性。

您可以重写您的方法签名以使用如下的委托:

public bool DoAnyValuesFail(Dictionary<string, int> dictionary,Func<int,bool> predicate)
{
     var failureValues = new List<KeyValuePair<string, int>>();   
     failureValues = dictionary.Where(x => predicate(x.Value)).ToList();
     return failureValues.Any();
     //instead of the code above you could simply do
     //return dictionary.Any(x => predicate(x.Value));
}

然后,当你称之为时,你会提供这样的表达式:

var myValues = new Dictionary<string, int> { { "Foo", 1 }, { "Bar", 6 } };
var failed = DoAnyValuesFail(myValues, x => x < 4); //4 is what you had as the failureValue