Why does Dictionary.ContainsKey throw ArgumentNullException? [closed]
Asked Answered
P

2

20

The documentation states that bool Dictionary<TKey, TValue>.ContainsKey(TKey key) throws an exception in case a null key is passed. Could anyone give a reason for that? Wouldn't it be more practical if it just returned false?

Pointenoire answered 6/12, 2013 at 15:45 Comment(13)
A null key is no value at all. It can never be used as a valid key so it can never be considered a valid input.Sphere
What is the value of null.GetHashCode()?Quietism
@Quietism it is whatever the IEqualityComparer says it is =p. It's actually entirely viable to choose to write a dictionary that accepts null keys, the library just didn't choose to do that.Instinctive
@Quietism Sure, then just instead if (key == null) throw new ArgumentNullExceltion; do if (key == null) return false;Pointenoire
@mkacz You should either support null keys or not support them. Returning false really does give a misleading impression (as stuartd answered). A better question would be, why not support null keys at all.Ossetic
Why are you asking this question? Is it just theoretical? What are you trying to do?Quillan
@Instinctive There's a good reason for that, though. Dictionary accepts both reference types and value types as keys, so it can't just write if (key == null) .... You can do the comparison, but it doesn't make any sense for value types.Angieangil
@Angieangil The dictionary has to go out of it's way to throw exceptions in the case where the key is null. It would be easier to support null keys; just pass all keys to the IEqualityComparer and let it either return a value or throw an exception. My guess is they needed to cast the key to object and then compare it to null in order to be able to add such a check, given that, as you say, the key can be a value type.Instinctive
I think not supporting null keys makes sense if your design philosophy is that null represents a kind of invalid value (like, well, a null pointer), rather than a valid bottom type. I guess that's the general philosophy of the .NET framework.Ossetic
@Instinctive Interestingly, using ILSpy seems to indicate that the code always does boxing of the key. Apparently, it is legal to compare a non-constrained generic type value to null. Strange.Angieangil
@Luaan: Every generic type T must either be a reference type, in which case it may be directly compared to null, a nullable type, in which case comparison to null can be "compared" to null using a HasValue check, or a structure type in which case a widening conversion will exist to T?, which may then be "compared" to null. To actually box the key if it's a non-nullable value type would be a little silly; it would seem either the JIT should optimize that away or a NullChecker<T>.IsNull method should be able to expedite the test.Isoleucine
@supercat: Yeah, it's probably the nullable lifting that allows this when the type is known at compile-time (ie. int test = 31; if (test == null) ...). However, the IL code still plainly states that there's a box instruction. The JIT will quite certainly optimize it away for reference types, but it's there in the IL. The IL code for the generic version is different than the one you get when the type is known (and no, it doesn't explicitly mention the Nullable at all, but that's due to the various Nullable optimization in the C# compiler).Angieangil
@Luaan: It would probably not be difficult for the JIT to recognize 99% of the cases where the result of a "box" is compared with null and abandoned before anything else is done with it. If it does so, then the comparison would look ugly in CIL but be comparatively harmless at run-time. If the thing actually did get boxed, then IEquatable<T> wouldn't be necessary (given generic value-type variables t1 and t2 and object o1, if one has performed o1 = t1;, then t2.Equals(o1)` won't be much slower than using the IEquatable override as `t2.Equals(t1).Isoleucine
G
22

If ContainsKey(null) returned false it would give the misleading impression that null keys are allowed..

Galenic answered 6/12, 2013 at 15:48 Comment(1)
And it probably also helps to locate your mistake sooner - since there's no way you should use a null reference in ContainsKey, you know there's a problem, rather than just getting a "nope, it's not here, keep trying". Yeah, I think I like it this way better :)Angieangil
S
7

This is how it is implemented: (Source)

public bool ContainsKey(TKey key) {
    return FindEntry(key) >= 0;
}

And the method FindEntry as:

private int FindEntry(TKey key) {
    if( key == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

    if (buckets != null) {
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
        for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
        }
    }
    return -1;
}

Since having a null value as key in dictionary is not allowed.

Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add(null, 10);

The above would produce an exception:

Value cannot be null. Parameter name: key

For your question:

Wouldn't it be more practical if it just returned false?

Someone from Microsoft could probably reply that. But IMO, since adding a null value for key is not allowed there is no point in checking for null key in ContainsKey

Shizukoshizuoka answered 6/12, 2013 at 15:54 Comment(1)
I expect the sole reason is probably that while IEquatable<T>.GetHashCode(T) could have been specified to return a constant when given a null value, it wasn't. If a Dictionary's call to IEquatable<T> is going to throw an exception when given a null argument, it would be better to have the exception thrown from Dictionary, giving the name of its parameter, than from IEquatable<T>.GetHashCode().Isoleucine

© 2022 - 2024 — McMap. All rights reserved.