在 C# 中查询字典的优雅方式

Posted

技术标签:

【中文标题】在 C# 中查询字典的优雅方式【英文标题】:Elegant way to query a dictionary in C# 【发布时间】:2017-11-27 02:42:40 【问题描述】:

我正在尝试创建一种优雅且可扩展的方式来查询将枚举映射到一组字符串的字典。

所以我有这个类SearchFragments,里面有字典。然后,我想要一个方法,让此类的消费者可以简单地询问“HasAny”,而这正是我苦苦挣扎的地方,只需传入一些查询,如表达式,然后得到布尔答案。

public class SearchFragments

    private readonly IDictionary<SearchFragmentEnum, IEnumerable<string>> _fragments;

    public SearchFragments()
    
        _fragments = new Dictionary<SearchFragmentEnum, IEnumerable<string>>();
    

    public bool HasAny(IEnumerable<SearchFragmentEnum> of)
    
        int has = 0;
        _fragments.ForEach(x => of.ForEach(y => has += x.Key == y ? 1 : 0));
        return has >= 1;
    

目前这种方式的问题是,这个类的消费者现在必须构造一个IEnumerable&lt;SearchFragmentEnum&gt;,这可能非常混乱。

我正在寻找的是消费代码将能够编写以下内容:

searchFragments.HasAny(SearchFragmentEnum.Name, SearchFragmentEnum.PhoneNumber)

但是该参数列表的大小可能会有所不同(我不必为每个可能的组合在 SearchFragments 类中编写方法重载(这样,如果在将来的某个日期将新值添加到 SearchFragmentEnum 中,我就不会了)不必更新课程。

【问题讨论】:

Why use the params keyword?的可能重复 试试这个:return of?.Any(_fragments.ContainsKey) ?? false; 【参考方案1】:

您可以使用params[]

public bool HasAny(params SearchFragmentEnum[] of)
 ...

旁注:您知道 LIN(Q) 查询应该只查询一个源而不会产生任何副作用吗?但是您的查询确实会不必要地增加整数:

_fragments.ForEach(x => of.ForEach(y => has += x.Key == y ? 1 : 0));

改用这个(这也更高效,更易读):

return _fragments.Keys.Intersect(of).Any();

一个更有效的替代方案是Sergey'sidea:

return of?.Any(_fragments.ContainsKey) == true; 

【讨论】:

谢谢你,我没有意识到这一点,有道理我猜这与 SOLID 的单一责任相同的逻辑原则。我的意思是我写的编译,但你提出了一个有效的观点。我会应用你在这里所说的:-) 我认为 intersect 类似于(至少在概念上)SQL 内连接是否正确?我的意思是它是两个集合的布尔交集? @ThomasCook: Intersect 是一个集合方法,用于构建两个序列的交集(通过忽略重复项)。但是由于它是延迟执行的,因此不需要构建整个交叉点。末尾的Any 将在找到后立即停止。如果您想知道有多少相交,您可以附加Count 而不是Any。但随后必须对所有内容进行评估。 相交是不必要的。它在 O(N+M) 中。这是一本字典。您可以简单地迭代输入数组并检查是否包含。 阅读Intersect 方法的作用。 github.com/Microsoft/referencesource/blob/master/System.Core/… - 简而言之,它采用第二个参数并将其转换为一个集合,然后遍历第一个参数并进行哈希查找。这是一个奇怪的解决方案,因为您的第一个参数已经 Dictionary - 它已经可以进行哈希查找! @adjan 之前的评论是正确的。你是对的,因为它被推迟了,所以它不需要构建整个交叉点 - 但它确实需要(不必要地)预先构建集合。【参考方案2】:

对于 c# 中的可变大小参数,您使用 params 关键字: public int HasAny(params SearchFragmentEnum[] of)

出于性能原因,.Net API 通常会提供几个重载;传递的参数被复制到一个新数组中。为最常见的情况显式提供重载可以避免这种情况。

public int HasAny(SearchfragmentEnum of1)
public int HasAny(SearchFragmentEnum of1, SearchFragmentEnum of2)
etc.

除了使用参数,您还可以考虑使用[Flags] 属性标记您的枚举。参数可以像HasAny(SearchFragmentEnum.Name | SearchFragmentEnum.PhoneNumber 一样传递。 *** 上的大量示例(例如 Using a bitmask in C#)

【讨论】:

【参考方案3】:

使用params 关键字允许不同数量的参数。此外,您可以通过循环更小的of 数组来简化代码。此外,您正在使用具有 O(1) 键检查的字典,因此不必有内部循环:

public bool HasAny(params SearchFragmentEnum[] of)

    foreach(var o in of) 
        if (this._fragments.ContainsKey(o))
            return true;
       
    return false;

使用 LINQ 更短

public bool HasAny(params SearchFragmentEnum[] of) 
    return of?.Any(_fragments.ContainsKey) ?? false;
 

【讨论】:

很好的解决方案!

以上是关于在 C# 中查询字典的优雅方式的主要内容,如果未能解决你的问题,请参考以下文章

有没有更优雅的方式将项目安全地添加到 Dictionary<> 中?

在 .NET 中序列化 MailMessage 对象的优雅方式

在 Hibernate 中实现基于条件的搜索页面的优雅方式

[C#] 如何优雅的解决 DBNull 问题

[C#] 如何优雅的解决 DBNull 问题

使用 ContentProvider 进行复杂原始查询的优雅方式