下面的代码打印“false”
IEnumerable<string> x = new List<string>();
Console.WriteLine(x.Contains(null));
但是下面的代码抛出了一个ArgumentNullException
:
IEnumerable<string> x = new Dictionary<string, string>().Keys;
Console.WriteLine(x.Contains(null));
我看到this post解释了为什么传入null时Dictionary.ContainsKey
会抛出,所以我猜这一行为是相关的。然而,在ContainsKey
的情况下,我得到了漂亮的绿色波浪线,而在IEnumerable
的情况下,我的应用程序崩溃了:
消费代码不会知道传递给它的IEnumerable
的底层类型,所以我们需要:
- 一般情况下不对可空类型使用
IEnumerable.Contains()
,或者 - 将
KeyCollection
转换为列表,然后将其视为IEnumerable
这是正确的,还是我错过了什么?
3条答案
按热度按时间l5tcr1uw1#
我假设您希望将
Keys
属性公开为IEnumerable<TKey>
序列,以允许搜索null
。一种简单的方法是将集合 Package 在IEnumerable<TKey>
实现中,该实现隐藏了集合的标识:使用示例:
这样,LINQ
Contains
操作符将不会检测到集合实现了ICollection<T>
接口,并且将遵循枚举集合并使用TKey
类型的默认比较器比较每个键的缓慢路径。这有两个缺点:1.这是一个复杂度为O(n)的算法。
Dictionary<K,V>.Comparer
的比较语义将被忽略。因此,如果字典被配置为不区分大小写,Contains
将执行区分大小写的搜索。这可能不是你想要的。一种更复杂的方法是将集合 Package 在
ICollection<TKey>
实现中,其中包括在Contains
方法中对null
的特殊处理:使用示例:
这样,生成的序列将保留基础集合的性能和行为特征。
你在问题中提到了第三种选择:使用
ToList
LINQ操作符将集合转换为List<T>
。这将创建键的副本,并将返回调用ToList
时键的快照。这可能是一个不错的选择,以防字典被冻结,并且键的数量很少。zpqajqem2#
这种行为背后的原因是,当
source
实现ICollection<TSource>
时,Enumerable.Contains<IEnumerable<TSource>>(this IEnumerable<TSource> source, TSource value)
有一个快捷方式。在这些情况下,它只需调用source
的Contains
方法。请参阅Enumerable.Contains
的参考源代码。字典的
Keys
属性是由KeyCollection
(引用源)实现的。在内部,它的Contains
方法包括对非空键的验证,这是您正在运行的。如果你想对
Contains
使用非快捷方式的linq方法,你应该总是能够通过在这个方法的另一个重载中提供一个comparer
参数来调用它--即使比较器是null
。mwngjboj3#
这两种方法都可以,但我推荐第三种方法:为你的项目启用nullable reference types。这样,你就不能把null传递给
IEnumerable<string>.Contains
,因为string
不允许空值。它是自文档化的,怎么样?如果你把警告当作错误,你得到的将不仅仅是绿色的曲线,你得到的将是真正的编译器错误,这意味着没有崩溃的可能性。