3

I've been struggling to Google this question as I can't get the wording quite right (hence the title).

The gist is why do one of the below work, is there a shorthand for test3:

 var test1 = new Dictionary<string, int>(); test1["Derp"] = 10; // Success var test2 = new Dictionary<string, List<int>>(); test2["Derp"].Add(10); // Fail var test3 = new Dictionary<string, List<int>>(); test3["Derp"] = new List<int>(); test3["Derp"].Add(10); // Success 

A scenario I'm coming across often is similar to the below (this is a very basic example):

 var names = new List<string>() { "Jim", "Fred", "Fred", "Dave", "Jim", "Jim", "Jim" }; var nameCounts = new Dictionary<string, int>(); foreach(var name in names) { if (!nameCounts.ContainsKey(name)) nameCounts.Add(name, 0); nameCounts[name]++; } 

In other words - is there a way to skip the "ContainsKey" check, and go straight to adding to my list (and key automatically)?

Edit: to be clear, I hadn't used the below as in my real-life situation, it isn't quite as simple (unfortunately!)

var nameCounts = names.GroupBy(x => x) .ToDictionary(x => x.Key, x => x.Count()); 
4
  • Use TryGetValue rather than ContainsKey. The general pattern I would recommend is dotnetfiddle.net/jPGk8E . Or consider MultiValueDictionary - github.com/dotnet/corefx/issues/1306 . Commented Jun 27, 2019 at 22:43
  • 3
    As the answers say, the way to do this is to write an extension method that has the semantics you want. But for your specific scenario, there is a better way to do that. You can just write the one-liner var nameCounts = names.GroupBy(x=>x).ToDictionary(x=>x.Key, x=>x.Count()); Commented Jun 27, 2019 at 22:57
  • Thanks @EricLippert. I knew somebody would throw the GroupBy at me. Unfortunately that wouldn't work in my real-life scenario as the code is a little more complicated than I could explain. Thank you, anyway! :) Commented Jun 27, 2019 at 23:24
  • 1
    You're welcome. If you have a "more complicated" scenario, my advice is to create a type that has the exact semantics you want. I do this all the time; in a recent project I needed to make a half a dozen different kinds of dictionaries, each with a slightly different semantics -- some were counting occurrences, some were maps from keys to lists, some were many-many relations, and so on -- so I made a wrapper type around the dictionary type for each, and gave each type exactly the signature for Add, and Get and so on, that I needed. It made the resulting code easy to write and read. Commented Jun 27, 2019 at 23:33

4 Answers 4

3

Perl calls this auto-vivification, and I use some extensions to Dictionary to implement various forms, you would need the one that uses a lambda to generate the initial values:

//*** // Enhanced Dictionary that auto-creates missing values with seed lambda // ala auto-vivification in Perl //*** public class SeedDictionary<TKey, TValue> : Dictionary<TKey, TValue> { Func<TValue> seedFn; public SeedDictionary(Func<TValue> pSeedFn) : base() { seedFn = pSeedFn; } public SeedDictionary(Func<TValue> pSeedFn, IDictionary<TKey, TValue> d) : base() { seedFn = pSeedFn; foreach (var kvp in d) Add(kvp.Key, kvp.Value); } public new TValue this[TKey key] { get { if (!TryGetValue(key, out var val)) base[key] = (val = seedFn()); return val; } set => base[key] = value; } } 

So then you could do test2 like so:

var test2 = new SeedDictionary<string, List<int>>(() => new List<int>()); test2["Derp"].Add(10); // works 

For your name counts example, you could use the version that auto-creates the default value for the value type:

//*** // Enhanced Dictionary that auto-creates missing values as default // ala auto-vivification in Perl //*** public class AutoDictionary<TKey, TValue> : Dictionary<TKey, TValue> { public AutoDictionary() : base() { } public AutoDictionary(IDictionary<TKey, TValue> d) : base() { foreach (var kvp in d) Add(kvp.Key, kvp.Value); } public new TValue this[TKey key] { get { if (!TryGetValue(key, out var val)) base[key] = val; return val; } set => base[key] = value; } } 
Sign up to request clarification or add additional context in comments.

6 Comments

Woazers - I see. I was hoping for something in-built, but I definitely want to try this as it's using a lot of techniques I don't get chance to use in my day-to-day. I'll also try and remember the term "autovivification" for when I want to explain why I'm going ott with my Dictionaries. Thank you :)
@Oyyou - A warning should be made on this implementation - it uses new to shadow the this indexer and that means that anything that casts this back to Dictionary<K, V> will use the standard implementation of the this indexer. It would be better to do a full implementation of IDictionary<K,V> instead.
@Enigmativity Wouldn't an implementation of IReadOnlyDictionary<K,V> and IDictionary also be needed? I'm not sure that behaving as a regular Dictionary in contexts that expect one is a bad thing...
@NetMage - The only requirements of IDictionary<K, V> are ICollection<KeyValuePair<K, V>>, IEnumerable<KeyValuePair<K, V>>, IEnumerable. I don't think an object behaving differently in different contexts is a good thing.
@Enigmativity Ah - you originally said "casts this back to Dictionary<K,V> and not IDictionary<K,V>... So, you don't think Dictionary<> should implement IReadOnlyDictionary or ExpandoObject implement IDictionary?
|
2

Another way you can do this (among many), is a little extension method (cutesy of Jon Skeet here)

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,TKey key) where TValue : new() { TValue ret; if (!dictionary.TryGetValue(key, out ret)) { ret = new TValue(); dictionary[key] = ret; } return ret; } 

Usage

strong textvar test2 = new Dictionary<string, List<int>>(); var myNewList = test2.GetOrCreate("Derp"); myNewList.Add(10); // or var test2 = new Dictionary<string, List<int>>(); test2.GetOrCreate("Derp").Add(10); // winning! 

Note : In all my early morning pep, i actually didn't look at this question, Eric Lippert is on the money in the comments, this can be simply done via a GroupBy and a projection to a dictionary with ToDictionary without all the extra fluff of extension methods and classes

Cutesy of Eric Lippert

// Count occurrences of names in a list var nameCounts = names.GroupBy(x => x) .ToDictionary(x => x.Key, x => x.Count()); 

Additional Resources

Enumerable.GroupBy Method

Groups the elements of a sequence.

Enumerable.ToDictionary Method

Creates a Dictionary<TKey,TValue> from an IEnumerable<T>.

6 Comments

I wouldn't stress about it; you answered the question that was asked, with a reasonable answer.
Now I wonder if a GetOrDefault to go with GetOrCreate would be better than my auto-vivifying wrappers...
@NetMage if you're worried, I'm worried
@Oyyou LOL. It's just a matter of style. I also considered an AutoRef class that creates a reference type automatically with an implicit conversion operator, but it won't work in most places (until Extension Everywhere is implemented perhaps).
@NetMage seems like a worthy addition to your class. I have secretly stashed yours away in a base lib of mine already ;) upvote
|
0

I usually do something like this:

TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) where TValue : new() => dict.TryGetValue(key, out TValue val) ? val : dict[key] = new TValue(); 

Edit: Another way is:

TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) where TValue : new() => dict.ContainsKey(key) ? dict[key] : dict[key] = new TValue(); 

I'm not sure if this is as performant, but it works on older C# versions, where my first example doesn't.

Comments

0

Alternative with C# 7 out variable :

foreach(var name in names) { nameCounts[name] = nameCounts.TryGetValue(name, out var count) ? count + 1 : 1; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.