使用数字操作数的字符串值创建自定义谓词[英] Creating a Custom Predicate using a String Value for the Numeric Operand

本文是小编为大家收集整理的关于使用数字操作数的字符串值创建自定义谓词的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我试图将数字操作数表达式的字符串值(" eslaythan","等于"等)传递到参数.我在下面创建了有效的代码,但这很"笨拙".我不喜欢if块,我认为有一种方法可以使用自定义的LINQ比较谓词来执行此操作.我试图遵循 这篇文章 ,但我似乎无法遵循它.关于如何清理我的方法的任何想法?

此处的代码显示我要如何将" eslaythan"的字符串值传递给函数

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

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

我认为下面的回答中的一部分混乱是,我并不能加快我对逆化,谓词和代表的术语的速度.对于那个很抱歉.无论如何,我确实想澄清一件事,那就是" eslaythan"," lysthan"和" equals"的值来自一个配置文件,因此它们需要是在运行时进行调整的"魔术字符串".

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

// 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而不是`弦.

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

本文地址:https://www.itbaoku.cn/post/1556977.html

问题描述

I am trying to pass in a string value ("GreaterThan", "Equals", etc.) for a numeric operand expression to a parameter. I have created the code below which works, but it's "clunky". I don't like the if blocks and I think there's a way to do this with a custom LINQ comparison predicate. I tried to follow the reply posted in this post, but I can't seem to follow it. Any ideas on how to clean up my method?

Here's code showing how I want to pass a string value of "GreaterThan" to a function

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

Here's the sample method that I wrote that's "clunky":

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

--- UPDATE - Final Version ---

I think part of the confusion in the responses below is that I'm not that up to speed on my terminology of fuctions, predicates and delegates. Sorry about that. Regardless, I did want to clarify one thing and that is that the values of "GreaterThan", "LessThan" and "Equals" come from a configuration file, so they need to be "Magic Strings" that adjust at run time.

Therefore, based on the feedback from Matthew Haugen and Enigmativity, I have come up with the following code that I think works best for my needs. I'm open to any suggestions if you think it's wrong or needs adjusted.

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

This then is my Compare extension method:

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

推荐答案

Given your requirement to match the expression against a string I would be inclined to do this:

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

However, as others have said I think this is a better option:

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

And then simply call it like so:

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

But then you are only one step away from making it even simpler:

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

No DoAnyValuesFail method required - meaning simpler code, less potential bugs, and no "magic" strings.

This code is much clearer and actually more terse than your original line.

其他推荐答案

I'd start by making it an enum rather than a `string.

public enum ComparisonType
{
    GreaterThan,
    LessThan,
    Equal,
}

Then, I'd change it to something like this. This will also improve performance, since only one matching value is required to return.

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

Of course, it's not all that much cleaner than what you've got. It's probably more reliable than depending on those string inputs, and that makes it a bit more readable. And not going via the List<> helps in my opinion. But I don't think there's much you can do beyond that. I mean, you could store the Func<T, bool> in a value that gets assigned to in the switch then use it afterward, which would normalize return dictionary.Any(...), but I feel like that would make it less readable.

Ultimately I think it's fine as it is. Anything you do with an Expression will just take away from readability with functionality this simple.

其他推荐答案

You could Rewrite your method signature to use a Delegate like this:

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

Then when you call it you supply the desired expression like this:

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