【问题标题】:Creating a Custom Predicate using a String Value for the Numeric Operand使用数字操作数的字符串值创建自定义谓词
【发布时间】:2014-10-16 00:00:21
【问题描述】:

我正在尝试将数字操作数表达式的字符串值(“GreaterThan”、“Equals”等)传递给参数。我已经创建了下面的代码,但它“笨拙”。我不喜欢 if 块,我认为有一种方法可以使用自定义 LINQ 比较谓词来做到这一点。我尝试关注 this post 中的回复,但似乎无法关注。关于如何清理我的方法的任何想法?

这里的代码显示了我想如何将字符串值“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”和“Equals”的值来自配置文件,因此它们需要是在运行时调整的“Magic Strings”。

因此,根据 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));
        }
    }
}

【问题讨论】:

  • 我想弄清楚你为什么要这样做。在我看来,与其创建和调用名为“DoAnyValuesFail”的方法,实际上将其编码为例如更具可读性。 "var failed = myValues.Values.Any(v => v > 4);"
  • 我还看到您使用“创建一个多余的对象”反模式对方法进行了编码。 IE。 “var failureValues = new List>();”稍后您将在其中为变量分配一些不同的值。不要那样做。
  • @PeterDuniho - 您的最后一条评论通常是一个很好的观点,但我怀疑在这种情况下,OP 试图抓住expression 不是三个选择之一的情况。如果没有初始声明,此代码将无法工作。
  • @Enigmativity:啊,是的。你是对的,在这种特殊情况下,由于代码的编写方式,他需要默认实例。也就是说,如果“failureValues”此时仍然为空,我认为最好初始化为 null 并在最后返回 false。另一方面,它是一个短暂的空 List,所以也许多余的对象在这里没有任何害处。第三方面,我仍然认为这是一种反模式。 :)
  • @PeterDuniho - 是的,我同意 - 这是一种反模式。会导致失败。

标签: c# linq predicate


【解决方案1】:

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

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 方法——这意味着更简单的代码、更少的潜在错误和没有“魔法”字符串。

这段代码比你原来的代码更清晰,实际上也更简洁。

【讨论】:

  • 非常感谢您提供这些信息。正如我在上面的帖子中更新的那样,“魔术字符串”来自配置文件,因此您使用返回 Func 是我正在寻找的,因此您得到了公认的答案。感谢您破译我要找的东西。让我知道您对我更新的代码的看法。
  • @bigmac - 没问题。我对你的代码的评论是你应该将你的 Compare 函数签名从 Func&lt;int, int, bool&gt; Compare(this string expression) 重构为 bool Compare(this string expression, int x, int failureValue) - 否则当你返回一个你立即调用的委托时代码会变得有点时髦。另一种选择是获取Compare() 结果的本地副本,然后在.Where 中调用它。这两个中的任何一个都可以避免Compare()(...) 双重调用。
【解决方案2】:

我首先将其设为enum 而不是`字符串。

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&lt;&gt; 会有所帮助。但我认为除此之外你无能为力。我的意思是,您可以Func&lt;T, bool&gt; 存储在分配给switch 的值中,然后使用它,这将使return dictionary.Any(...) 正常化,但我觉得这样会减少它可读。

最终我认为这很好。您对 Expression 所做的任何事情都会因为功能如此简单而降低可读性。

【讨论】:

  • 马修,也感谢您提供的信息。正如我在帖子中所说,我正在使用您的回应和 Enigmativity 的混合体。因为我只能接受一个答案,所以我接受了他的答案,因为他帮助我指出了使用 Func (我不知道我能做到的事情)。无论如何,我感谢您的反馈!
【解决方案3】:

您可以重写您的方法签名以使用这样的委托:

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

【讨论】:

  • 特里,感谢您的输入,但您的代码似乎对我没有帮助,因为您将 x =&gt; x &lt; 4 硬编码到参数中,我真的需要 &lt; 值从配置字符串中解析。如果我在您的代码中遗漏了什么,请告诉我。
  • 您传递给方法的 func 类型的参数等同于(意味着完成工作)您通过传递字符串和失败值所获得的内容。例如在您的代码中您像这样调用 doAnyValuesFail:'DoAnyValuesFail(myValues, "GreaterThan", 4);',在我的示例中,通过将委托传递给方法并且不需要测试字符串或失败值的附加值,它相当于: 'DoAnyValuesFail(myValues, x => x > 4);'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-17
  • 2017-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-03
相关资源
最近更新 更多