以下代码打印“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));
我看到 这篇文章 解释了为什么
Dictionary.ContainsKey
在传入 null 时抛出,所以我猜这种行为是相关的。但是,在 ContainsKey
的情况下,我得到漂亮的绿色波浪形,而在 IEnumerable
的情况下,我的应用程序崩溃了:
消费代码不会知道传递给它的
IEnumerable
的底层类型,所以我们需要:
IEnumerable.Contains()
和可空类型 orKeyCollection
转换为列表,然后再将它们视为IEnumerable
这是对的,还是我遗漏了什么?
你可以做其中任何一个,但我推荐第三种方法:为你的项目启用 nullable reference types。这样,您就不能将空值传递给
IEnumerable<string>.Contains
,因为string
不允许空值。它是自我记录的,怎么样。
如果您将警告视为错误,您将得到的不仅仅是绿色波浪线,而是实际的编译器错误,这意味着没有崩溃的可能性。
Keys
属性公开为允许搜索 IEnumerable<TKey>
的 null
序列。一种简单的方法是将集合包装在 IEnumerable<TKey>
实现中,隐藏集合的身份:
static IEnumerable<T> HideIdentity<T>(this IEnumerable<T> source)
{
ArgumentNullException.ThrowIfNull(source);
foreach (var item in source) yield return item;
}
用法示例:
IEnumerable<string> x = new Dictionary<string, string>().Keys.HideIdentity();
Contains
运算符将不会检测到集合实现了 ICollection<T>
接口,并且将遵循枚举集合并使用 TKey
类型的默认比较器比较每个键的缓慢路径。这有两个缺点:
Dictionary<K,V>.Comparer
的比较语义将被忽略。因此,如果字典配置为不区分大小写,Contains
将执行区分大小写的搜索。这可能不是你想要的。更复杂的方法是将集合包装在
ICollection<TKey>
实现中,其中包括在 null
方法中对 Contains
的特殊处理:
class NullTolerantKeyCollection<TKey, TValue> : ICollection<TKey>
{
private readonly Dictionary<TKey, TValue>.KeyCollection _source;
public NullTolerantKeyCollection(Dictionary<TKey, TValue>.KeyCollection source)
{
ArgumentNullException.ThrowIfNull(source);
_source = source;
}
public int Count => _source.Count;
public bool IsReadOnly => true;
public bool Contains(TKey item) => item == null ? false : _source.Contains(item);
public void CopyTo(TKey[] array, int index) => _source.CopyTo(array, index);
public void Add(TKey item) => throw new NotSupportedException();
public bool Remove(TKey item) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public IEnumerator<TKey> GetEnumerator() => _source.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
static NullTolerantKeyCollection<TKey, TValue> NullTolerant<TKey, TValue>(
this Dictionary<TKey, TValue>.KeyCollection source)
{
return new NullTolerantKeyCollection<TKey, TValue>(source);
}
用法示例:
IEnumerable<string> x = new Dictionary<string, string>().Keys.NullTolerant();
这样生成的序列将保留底层集合的性能和行为特征。
您在问题中提到了第三个选项:使用
List<T>
LINQ 运算符将集合转换为 ToList
。这将创建密钥的副本,并将在调用 ToList
时返回密钥的快照。如果字典被冻结并且键的数量很少,这可能是一个不错的选择。