I assume that you want to expose the Keys
property as an IEnumerable<TKey>
sequence that allows searching for null
. An easy way to do it is to wrap the collection in an IEnumerable<TKey>
implementation, that hides the identity of the collection:
static IEnumerable<T> HideIdentity<T>(this IEnumerable<T> source)
{
ArgumentNullException.ThrowIfNull(source);
foreach (var item in source) yield return item;
}
Usage example:
IEnumerable<string> x = new Dictionary<string, string>().Keys.HideIdentity();
This way the LINQ Contains
operator will not detect that the collection implements the ICollection<T>
interface, and will follow the slow path of enumerating the collection and comparing each key using the default comparer of the TKey
type. There are two downsides to this:
- The CPU complexity of the operation will be O(n) instead of O(1).
- The comparison semantics of the
Dictionary<K,V>.Comparer
will be ignored. So if the dictionary is configured to be case-insensitive, the Contains
will perform a case-sensitive search. This might not be what you want.
A more sophisticated approach is to wrap the collection in an ICollection<TKey>
implementation, that includes special handling for the null
in the Contains
method:
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);
}
Usage example:
IEnumerable<string> x = new Dictionary<string, string>().Keys.NullTolerant();
This way the resulting sequence will preserve the performance and behavior characteristics of the underlying collection.
You mentioned a third option in the question: converting the collection to a List<T>
with the ToList
LINQ operator. This will create a copy of the keys, and will return a snapshot of the keys at the time the ToList
was called. It might be a decent option in case the dictionary is frozen, and the number of keys is small.