如何将谓词作为参数传递到 C# 中

9
我该如何将谓词传递给一个方法,但也使其在没有传递谓词的情况下正常工作?我认为可以尝试类似以下代码,但这好像不正确。
private bool NoFilter() { return true; }

private List<thing> GetItems(Predicate<thing> filter = new Predicate<thing>(NoFilter))
{
    return rawList.Where(filter).ToList();
}

你是在询问默认值吗?否则,将NoFilter定义为Predicate<things>并传递它。 - Sinatr
创建一个重载 GetItems() { return GetItems(NoFilter); } - Lee
@Servy,我从未想过new Predicate<thing>(NoFilter)在代码编译时就被解析了。现在你这么说,我才明白。谢谢。 - Fabulous
@Joel Coehoorn 这个问题为什么不是重复的?它与重复的问题完全一样。 - Servy
@Servy 实际上,正如标题所说,我正在询问如何将谓词作为参数传递。我加入了可选部分,以查看如何处理空谓词(这在被接受的答案中得到了很好的解决)。 - davecove
显示剩余7条评论
2个回答

16
private List<thing> GetItems(Func<thing, bool> filter = null)
{
    return rawList.Where(filter ?? (s => true)).ToList();
}

在这个表达式中,s => true 是后备过滤器,如果参数 filter 为 null,则对其进行评估。它只是将列表的每个条目(作为 s)取出并返回 true


当我实现时出现这样的错误:无法将System.Predicate<thing>转换为System.LinQ.Expressions.Expression<SystemFunc<thing,bool>> - davecove
1
尝试使用 Func<Thing, bool> 而不是 Predicate<Thing> - Waescher
对于无警告使用:Func<thing, bool>? filter = null - Honza P.

3

这个问题有两个部分。

首先,您需要调整 NoFilter() 函数以与 Predicate<T> 兼容。注意,后者是泛型的,但前者不是。将 NoFilter() 更改为以下内容:

private bool NoFilter<T>(T item) { return true; }

我知道你从不使用泛型类型参数,但是这是必要的,以使其与你的谓词签名兼容。

有趣的是,你也可以通过以下方式定义NoFilter

private Predicate<T> NoFilter = x => true;

现在是第二部分:我们可以考虑使用新的通用方法作为GetItems()的默认参数。这里有一个技巧,你只能使用常量。虽然NoFilter()永远不会改变,但从编译器的角度来看,它并不完全等同于正式的常量。实际上,你只能使用一种可能的常量: null。这意味着你的方法签名必须像这样:
private List<thing> GetItems(Predicate<thing> filter = null)

然后你可以在函数开头检查 null 并将其替换为 NoFilter:

private List<thing> GetItems(Predicate<thing> filter = null)
{
    if (filter == null) filter = NoFilter;
    return rawList.Where(filter).ToList();
}

如果您想在调用方法时显式地传递这个参数,可以这样做:

var result = GetItems(NoFilter);

这应该完全回答了最初的问题,但我不想就此停止。让我们深入了解一下。

既然您现在已经需要if条件,那么此时我倾向于完全删除NoFilter<T>()方法,并只执行以下操作:

private IEnumerable<thing> GetItems(Predicate<thing> filter = null)
{
    if (filter == null) return rawList;
    return rawList.Where(filter);
}

请注意,我还更改了返回类型并删除了最后的ToList()调用。如果你发现自己在函数末尾调用ToList()以匹配List<T>返回类型,那么更改方法签名为返回IEnumerable<T>几乎总是更好的选择。如果你真的需要一个列表(通常不需要),你仍然可以在调用函数后调用ToList()
这个改变使你的方法更加有用,给你一个更抽象的类型,更适合其他接口,并且可能会带来显著的性能提升,无论是在降低内存使用方面还是在惰性求值方面。
最后一个补充是,如果你只使用IEnumerable,我们可以看到这个方法实际上除了基本的rawItems字段之外没有太多价值。你可以考虑将其转换为属性,像这样:
public IEnumerable<T> Items {get {return rawList;}}

这仍然允许您的类型的消费者使用.Where()方法来使用谓词(或不使用),同时继续隐藏底层的原始数据(您不能直接调用.Add()等)。

2
我在传递给.Where()的after筛选器中遇到了一个错误。假设rawList是字符串对象列表...无法将System.Predicate<String>转换为System.Func<string,bool>..难道我是唯一一个遇到这个问题的人吗? - m1nkeh
Predicate<X>指的是与Func<X, bool>相同的东西。 它应该是可以转换的,或者可能缺少重载或引用。否则,它只是一个简单的更改。 - Joel Coehoorn
嗯...我感觉我漏掉了一些基础知识,甚至无法确定传递给GetItems()的东西的形状是什么...我想我会打开一个新的问题! - m1nkeh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接