39

c# .NET 2.0 有什么办法!组合多个谓词?

假设我有以下代码。

List<string> names = new List<string>();
names.Add("Jacob");
names.Add("Emma");
names.Add("Michael");
names.Add("Isabella");
names.Add("Ethan");
names.Add("Emily");

List<string> filteredNames = names.FindAll(StartsWithE);

static bool StartsWithE(string s)
{
    if (s.StartsWith("E"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

这给了我:

Emma
Ethan
Emily

所以这是很酷的东西,但我知道希望能够使用多个谓词进行过滤。

所以我希望能够这样说:

List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI);

为了得到:

Emma
Isabella
Ethan
Emily

我怎样才能做到这一点?目前我只是对完整列表进行两次过滤,然后再组合结果。但不幸的是,这是非常低效的,更重要的是我丢失了原始排序顺序,这在我的情况下是不可接受的。

我还需要能够迭代任意数量的过滤器/谓词,因为可能有很多。

同样,它需要是 .NET 2.0 解决方案,不幸的是我不能使用更新版本的框架

非常感谢。

4

7 回答 7

67

怎么样:

public static Predicate<T> Or<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (predicate(item))
            {
                return true;
            }
        }
        return false;
    };
}

为了完整性:

public static Predicate<T> And<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (!predicate(item))
            {
                return false;
            }
        }
        return true;
    };
}

然后调用它:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI));

另一种选择是使用多播委托,然后使用 拆分它们GetInvocationList(),然后做同样的事情。然后你可以这样做:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI));

不过,我不是后一种方法的忠实拥护者——感觉有点像滥用多播。

于 2009-08-08T07:34:58.860 回答
37

我想你可以这样写:

Func<string, bool> predicate1 = s => s.StartsWith("E");
Func<string, bool> predicate2 = s => s.StartsWith("I");
Func<string, bool> combinedOr = s => (predicate1(s) || predicate2(s));
Func<string, bool> combinedAnd = s => (predicate1(s) && predicate2(s));

... 等等。

于 2012-10-30T06:40:59.073 回答
10

我最近想出了一个类似于这个问题的解决方案,这也可能会有所帮助。我为列表扩展了 FindAll 方法,允许我根据需要在列表中堆叠谓词:

public static class ExtensionMethods
{
    public static List<T> FindAll<T> (this List<T> list, List<Predicate<T>> predicates)
    {
        List<T> L = new List<T> ();
        foreach (T item in list)
        {
            bool pass = true;
            foreach (Predicate<T> p in predicates)
            {
                if (!(p (item)))
                {
                    pass = false;
                    break;
                }
            }
            if (pass) L.Add (item);
        }
        return L;
    }
}

它返回一个列表,其中仅包含与所有给定谓词匹配的项目。当然,它可以很容易地更改为 OR 所有谓词而不是 AND。但仅凭这一点,人们就可以组合出相当多的逻辑组合。

用法:

{
    List<Predicate<int>> P = new List<Predicate<int>> ();
    P.Add (j => j > 100);
    P.Add (j => j % 5 == 0 || j % 7 == 0);
    P.Add (j => j < 1000);

    List<int> L = new List<int> () { 0, 1, 2, ... 999, 1000 }
    List<int> result = L.FindAll (P);

    // result will contain: 105, 110, 112, 115, 119, 120, ... 994, 995 
}
于 2017-06-05T23:30:03.503 回答
1

在 .NET 2.0 中,您可以使用匿名委托:

List<string> filteredNames = names.FindAll(
   delegate(string s) { return StartsWithE(s) OR StartsWithI(s); }
);

事实上,你也可以用它来替换你的函数:

List<string> filteredNames = names.FindAll(
   delegate(string s) { return s.StartsWith("E") || s.StartsWith("I"); }
);
于 2009-08-08T07:35:09.813 回答
0

您可以创建第三个谓词,在内部将结果 OR 在一起。我认为您可以使用 lambda 表达式即时执行此操作。像这样的东西(这不是一个 lambda 表达式,因为我不太擅长那个 snytax):

static bool StartsWithEorI(string s)
{
    return StartsWithE(s) || StartsWithI(s);
}
于 2009-08-08T07:23:04.347 回答
0

您可以将谓词方法包装到一个类中,并让构造函数接受一个字符串数组来测试:

class StartsWithPredicate
{
    private string[] _startStrings;
    public StartsWithPredicate(params string[] startStrings)
    {
        _startStrings = startStrings;
    }
    public bool StartsWith(string s)
    {
        foreach (var test in _startStrings)
        {
            if (s.StartsWith(test))
            {
                return true;
            }
        }
        return false;
    }
}

然后你可以像这样拨打电话:

List<string> filtered = names.FindAll((new StartsWithPredicate("E", "I")).StartsWith);

这样您就可以测试输入字符串的任意组合,而无需使用该StartsWith方法的新变体来扩展代码库。

于 2009-08-08T07:34:48.097 回答
0

在上面的“params”数组方法中广泛使用了这种模式后,我对最近了解多播委托很感兴趣。由于委托本身支持列表(或多播),您可以跳过 params[] 模式,只需为您的 Test() 函数提供一个委托。您需要在提供的 Predicate<> 上调用 GetInvokationList。请参阅:Func 类型的多播委托(带返回值)?

于 2018-01-10T23:59:10.783 回答