5

When I first started to program in C# last year, I immediately looked for the equivalent to STL's map, and learned about Dictionary.

UPDATE crossed out this garbage below, I was completely wrong. My experience with STL's map was that I hated when I requested it for a value, and if the key wasn't in the map, it would automatically create the value type (whatever its default constructor did) and add it to the map. I would have to then check for this condition in code and throw an exception.

Dictionary<> gets the whole shebang correct -- if the key isn't there, it throws the exception if you're requesting the value, or automatically adds it if it's not and you want to set the value.

But you all already knew that. I should have written my unit tests before posting here and embarrassing myself. :) They're written now!

Now I love Dictionary and all, but the thing that bugs me the most about it right now is that if the key isn't in the Dictionary, it throws a KeyNotFoundException. Therefore, I always need to write code like this:

Dictionary<string,string> _mydic;

public string this[string key]
{
  get {
    return _mydic[key]; // could throw KeyNotFoundException
  }
  set {
    if( _mydic.ContainsKey( key))
      _mydic[key] = value;
    else
      _mydic.Add( key, value);
  }
}

Why doesn't Dictionary automatically add the key value pair if the key doesn't exist, like STL's map?

Now the funny thing is that in a previous life, I used to get annoyed because I'd often have to try to prevent map from doing just that. I guess my use cases right now are a little different.

Dave
  • 14,618
  • 13
  • 91
  • 145
  • 5
    My gut answer would be because `Dictionary` was never designed to replicate the STL `map`. – Jeff Yates Mar 05 '10 at 18:01
  • I'm sure it wasn't, but I'm just curious why they decided not to design it to behave similarly in the absence of the specified key. – Dave Mar 05 '10 at 18:02
  • I think you misplaced some instructions that should be on getter inside the setter. – Jorge Ferreira Mar 05 '10 at 18:03
  • @smink: I'm okay with accessing an invalid key bubbling up the KeyNotFoundException. But for setting, I want the STL map behavior -- if it's there, overwrite it. If it's not, add it. – Dave Mar 05 '10 at 18:08
  • I'm not convinced that an STL map handles the [] operator any differently. See MSDN - http://msdn.microsoft.com/en-us/library/fe72hft9(VS.80).aspx – Jeff Yates Mar 05 '10 at 18:08
  • 1
    @Jeff: it does in that it automatically adds the key if it's not there, even if you're requesting the value. At least that's what I remember (but of course we all know what that means now). – Dave Mar 05 '10 at 18:32
  • @Dave: Write yourself a neat little `public static Value GetOrCreate(this IDictionary dict, Key key)`. In C++, I always seem to have to use `std::map::find()` because that stupid `std::map::operator[]()` adds missing values. In C#, I always seem to need `GetOrCreate`, since no operation as easy as that beautiful `std::map::operator[]()` is available. Such is life. – sbi Mar 05 '10 at 18:38
  • @Dave: We live and learn. If this is the most embarrassing question you ask, you are a lucky lucky dev. – Jeff Yates Mar 05 '10 at 19:33

3 Answers3

10

Which value would you want it to return if it didn't exist in the map already? It could return default(TValue) - but then it would be somewhat easy to accidentally use a value assuming it was correct. Note that if you want that behaviour, you can use:

get {
  string value;
  if (!_mydic.TryGetValue(key, out value))
    value = default(string);
  return value;
}

I wouldn't personally recommend it in general, but if you're happy not to be able to distinguish (via this indexer) between a key with a null value and a missing key, it will do the trick.

Note that your setter can me a lot simpler - just

_mydic[key] = value;

will overwrite if necessary.

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Jon, I'm a little confused. The reason why I have all of that logic in the setter is because of the exception that gets thrown. So I can only use the setter code you had specified if the key already exists... right? – Dave Mar 05 '10 at 18:06
  • 2
    No--the [] setter will add or overwrite as needed. – Michael Haren Mar 05 '10 at 18:09
  • 1
    @Dave: See http://msdn.microsoft.com/en-us/library/9tee9ht2.aspx. " If the specified key is not found, a get operation throws a KeyNotFoundException, and **a set operation creates a new element with the specified key**." – Jeff Yates Mar 05 '10 at 18:10
  • @Dave - The exception will only be thrown if you try to query the Dictionary with a key that doesn't exist. If you use the Indexer set... it will Add if it doesn't exist. See the documentation here for more information http://msdn.microsoft.com/en-us/library/bb302460.aspx – Nick Mar 05 '10 at 18:11
  • @Jon Skeet: I know for `Dictionary` my edit does the same thing, but it's a generally good idea to treat the `out` parameter of `TryGet*` as uninitialized. This pattern is consistent for any default/unset value as well. – Sam Harwell Mar 05 '10 at 18:15
  • 2
    @280Z28: WHy? The rule on an `out` parameter is that the method MUST set it for all execution paths. – Jeff Yates Mar 05 '10 at 18:16
  • OK guys, sorry. My memory is bad. I used to have issues with map NOT throwing an exception when the key isn't there, so I'd have to test map for existence so I could then throw an exception. Actually Dictionary is different than map, but it's actually better IMO. – Dave Mar 05 '10 at 18:26
  • @Jeff Yates: Must set it to what? What's the difference between uninitialized, and initialized to an unknown value? (Like I said, for `Dictionary`, it clearly states this value is set to `default(K)`, but if you code like I put above it's clear exactly what you mean: "I wan't to use this default value if the lookup failed") – Sam Harwell Mar 05 '10 at 18:53
  • @280Z28: The documentation for `TryGetValue` documents what the value is so it is clearly not an unknown value (and I would shy away from any API that doesn't document its out parameters fully). That said, if you want a difference default, you'd need an `else` block to set it. – Jeff Yates Mar 05 '10 at 19:32
3

I agree handling the exception is kind of annoying, especially for reference types when getting a null usually makes sense. In either case though you can use Dictionary<>.TryGetValue() to get the value in one call.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Frank Schwieterman
  • 24,142
  • 15
  • 92
  • 130
0

I'm confused. It does, here. The docs even say so:

When you set the property value, if the key is in the Dictionary(TKey, TValue), the value associated with that key is replaced by the assigned value. If the key is not in the Dictionary(TKey, TValue), the key and value are added to the dictionary.

and

KeyNotFoundException: The property is retrieved and key does not exist in the collection.

(no mention of it being thrown during assignment).

Ken
  • 1,261
  • 2
  • 9
  • 7