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

时间:2014-10-16 00:00:22

标签: c# linq predicate

我正在尝试将数字操作数表达式的字符串值(“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));
        }
    }
}

3 个答案:

答案 0 :(得分:1)

我首先将它设为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<>帮助。但我认为除此之外你还能做很多事情。我的意思是,您可以Func<T, bool>存储在switch中分配的值中,然后再使用它,这会使return dictionary.Any(...)正常化,但我觉得这样会降低可读性。

最终我觉得它很好。使用Expression执行的任何操作都会使功能性的可读性变得如此简单。

答案 1 :(得分: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方法 - 意味着更简单的代码,更少的潜在错误,以及没有&#34;魔法&#34;字符串。

此代码更清晰,实际上比原始代码更简洁。

答案 2 :(得分:0)

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

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