11

Given the following stack trace:

MESSAGE: Value cannot be null.Parameter name: key  
SOURCE: mscorlib  
TARGETSITE: Void ThrowArgumentNullException(System.ExceptionArgument)  
STACKTRACE:  
   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)  
   at System.Collections.Generic.Dictionary'2.FindEntry(TKey key)  
   at System.Collections.Generic.Dictionary'2.get_Item(TKey key)  
   at MyCompany.MAF.Agent.ServiceContracts.ConvertUtils.Convert(Dictionary'2 from) in D:\Development\MAF\Agent\MyCompany.MAF.Agent\ServiceContracts\ConvertUtils.cs:line 11

I conclude that somehow the following block of code has retrieved a null from the input Dictionary's Keys collection. However, the input dictionary is an instance of Dictionary<string, string>. The implementation of Dictionary<string, string> makes that condition impossible. Upon adding an item with a null key, an exception is thrown.

internal static KeyValuePair<string, string>[] Convert(IDictionary<string, string> from)
{
    List<KeyValuePair<string, string>> ret = new List<KeyValuePair<string, string>>();
    foreach (string key in from.Keys)
        ret.Add(new KeyValuePair<string, string>(key, from[key]));
    return ret.ToArray();
}
Jeroen
  • 60,696
  • 40
  • 206
  • 339
goofballLogic
  • 37,883
  • 8
  • 44
  • 62
  • Since it doesn't really answer your question (my best guess is that it's thread related) but your loop is redundant from.ToArray() will yield the same result – Rune FS Feb 17 '10 at 19:04

6 Answers6

19

I've had this problem happen frequently because I made the mistake of allowing multiple threads to access the same dictionary. Make sure that this is not the case, because Dictionary is not thread-safe.

(Incidentally, your method can be greatly simplified. Dictionary<K,V> is already an IEnumerable<KeyValuePair<K,V>>. You should be able to just do ToArray on one.

Jacob
  • 77,566
  • 24
  • 149
  • 228
  • I have tried and failed to create a race condition with threads. Even if there was a threading problem, it would never be possible for key to be initialised to null from the Keys collection – goofballLogic Feb 17 '10 at 18:55
  • Good point about the ToArray method - this doesn't explain the exception, but I suspect it would prevent it from happening. – goofballLogic Feb 17 '10 at 18:57
  • No, you cannot have a null key, but the internal methods used by `Dictionary` can still throw this exception when entering into certain states. I've seen it happen a lot, especially when using unprotected static dictionaries in my ASP.NET applications. – Jacob Feb 17 '10 at 18:58
  • @goofballlogic: That's what one would expect but check the link in my answer for a race condition that results in a Queue.Dequeue() returning a null item when the Queue never contained a null. Without thread safety on these non-thread safe objects the behavior becomes very unexpected. – Cory Charlton Feb 17 '10 at 19:00
  • Good point. The exception could be due to a clash internal to the dictionary itself caused by multiple threads. Seems unlikely, but it could happen... – goofballLogic Feb 17 '10 at 19:01
  • For bonus points... any idea how to unit test for this? I've tried and failed so far. – goofballLogic Feb 17 '10 at 19:02
  • 3
    +1, If you don't protect it with a lock, threading can destroy the internal structure and create null entries in the buckets. Dictionary is most vulnerable when it re-organizes itself when the number of elements grows enough to require more buckets. – Hans Passant Feb 17 '10 at 19:07
  • 1
    I tried before and also failed. Made me wonder if it wasn't so much about threading as much as ASP.NET application pooling. But I learned my lesson. Once I placed locks on static member access code or migrated to storing things in an Application object, these issues went away. – Jacob Feb 17 '10 at 19:09
3

It looks more like that your IDictionary argument has an item with a Key parameter which is null.

The parameter checking for the IDictionary will probably be happening somewhere internally in the framework.

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • 1
    Dictionary can not be populated with an item with a null key – goofballLogic Feb 17 '10 at 18:51
  • @goofballogic, true but you'd only find out at runtime if your key string is null and then get an exception... – Tony The Lion Feb 17 '10 at 18:54
  • 1
    No, Dictionary throws an exception if something attempts to populate it with a null key. The exception shown is retrieving the value, not adding/inserting it. – goofballLogic Feb 17 '10 at 18:56
  • 1
    @goofballlogic the argument type is IDictionary not Dictionary and the former _can_ have a null key (there's nothing in the interface prohibiting that). Do you know that the argument being passed is actually a Dictionary? – Rune FS Feb 17 '10 at 18:58
  • 1
    Yes this is only referenced from one point in the entire code base which is passing in a Dictionary – goofballLogic Feb 17 '10 at 18:59
3

Threading might well be the cause, but it was a red herring in our case. You can also seem to have this problem if you have a Release build where a function was inlined. Consider this repro:

class Program
{
    static string someKey = null;

    static void Main(string[] args)
    {
        try
        {
            object thing;

            if (SomeSeeminglyUnrelatedFunction(someKey, out thing)) Console.WriteLine("Found");

            things.TryGetValue(someKey, out thing);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.StackTrace.ToString());
            throw;
        }

        Console.ReadKey();
    }

    private static Dictionary<string, object> stuff = new Dictionary<string, object>();
    private static Dictionary<string, object> things = new Dictionary<string, object>();

    private static bool SomeSeeminglyUnrelatedFunction(string key, out object item)
    {
        return stuff.TryGetValue(key, out item);
    }
}

On my machine this gives the following Debug build stack trace:

at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at MyApp.Program.SomeSeeminglyUnrelatedFunction(String key, Object& item)
at MyApp.Program.Main(String[] args)

But, it gives this stack trace in Release build:

at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at MyApp.Program.Main(String[] args)

You'd be tempted to think with that last stack trace that it has to be things.TryGetValue that causes the exception, when in fact it was the SomeSeeminglyUnrelatedFunction function that got inlined in Main.

So if you land here, please consider if you have a similar issue. (I realize this may not be a straight up answer to OP's question, but wanted to share nonetheless as it might help a future visitor.)

Jeroen
  • 60,696
  • 40
  • 206
  • 339
2

This exception happens if the dictionary key is null. As the built-in Dictionary class doesn't allow such keys, you might be using your own IDictionary-compatible class which allows null.

AndiDog
  • 68,631
  • 21
  • 159
  • 205
2

Not sure about the null but why aren't you using:

internal static KeyValuePair<string, string>[] Convert(IDictionary<string, string> from)
{
    return from.ToArray();
}

Edit: As far as the null values are concerned. Do you have multiple threads accessing this IDictionary? Corruption is possible if you're not being thread safe. See this post for an example of corruption in the

Queue<T>

class. How can I modify a queue collection in a loop?

Community
  • 1
  • 1
Cory Charlton
  • 8,868
  • 4
  • 48
  • 68
  • Good point - this doesn't explain the exception, but I suspect it would prevent it from happening. – goofballLogic Feb 17 '10 at 18:57
  • @goofballlogic: See my update. If the IDictionary is getting corrupt then simplifing the method might not be the solution. You might need to implement some locking around the IDictionary. – Cory Charlton Feb 17 '10 at 18:58
1

Could it possible be that another thread is affecting the dirctionary being passed into Convert?

Dan OConnell
  • 217
  • 1
  • 8