Related
Assuming one has a method producing IReadOnlyDictionary<DerivedType, object>, how does one provide this object to a method taking IReadOnlyDictionary<BaseType, object>? Why does casting not work?
IReadOnlyDictionary<TKey, TValue> does not have covariant parameters for two reasons. First, TKey cannot be covariant because it is also used in an input position (in ContainsKey), and both TKey and TValue cannot be covariant because it also implements IEnumerable<KeyValuePair<TKey, TValue>> and KeyValuePair<TKey, TValue> is a value type and thus invariant.
Fortunately, even though the runtime cannot cast between the types, it is easy to adapt one to the other. First, if the underlying collection is actually a Dictionary<DerivedType, object>, this is one of the cases when one could inherit from Dictionary:
class BaseKeyDictionary<TBaseKey, TKey, TValue> : Dictionary<TKey, TValue>, IReadOnlyDictionary<TBaseKey, TValue> where TKey : class, TBaseKey
{
TValue IReadOnlyDictionary<TBaseKey, TValue>.this[TBaseKey key] => key is TKey key2 ? this[key2] : throw new KeyNotFoundException();
IEnumerable<TBaseKey> IReadOnlyDictionary<TBaseKey, TValue>.Keys => ((IReadOnlyDictionary<TKey, TValue>)this).Keys;
IEnumerable<TValue> IReadOnlyDictionary<TBaseKey, TValue>.Values => ((IReadOnlyDictionary<TKey, TValue>)this).Values;
int IReadOnlyCollection<KeyValuePair<TBaseKey, TValue>>.Count => Count;
bool IReadOnlyDictionary<TBaseKey, TValue>.ContainsKey(TBaseKey key)
{
return key is TKey key2 ? ContainsKey(key2) : false;
}
IEnumerator<KeyValuePair<TBaseKey, TValue>> IEnumerable<KeyValuePair<TBaseKey, TValue>>.GetEnumerator()
{
foreach(var pair in this)
{
yield return new KeyValuePair<TBaseKey, TValue>(pair.Key, pair.Value);
}
}
bool IReadOnlyDictionary<TBaseKey, TValue>.TryGetValue(TBaseKey key, out TValue value)
{
if(key is TKey key2) return TryGetValue(key2, out value);
value = default;
return false;
}
}
And, if one does not have access to the underlying type of the dictionary, it is always possible to create a wrapper for it to implement the interface via the original object:
class BaseKeyDictionaryWrapper<TBaseKey, TKey, TValue> : IReadOnlyDictionary<TBaseKey, TValue> where TKey : class, TBaseKey
{
readonly IReadOnlyDictionary<TKey, TValue> dict;
public BaseKeyDictionaryWrapper(IReadOnlyDictionary<TKey, TValue> dict)
{
this.dict = dict;
}
TValue IReadOnlyDictionary<TBaseKey, TValue>.this[TBaseKey key] => key is TKey key2 ? dict[key2] : throw new KeyNotFoundException();
IEnumerable<TBaseKey> IReadOnlyDictionary<TBaseKey, TValue>.Keys => dict.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TBaseKey, TValue>.Values => dict.Values;
int IReadOnlyCollection<KeyValuePair<TBaseKey, TValue>>.Count => dict.Count;
bool IReadOnlyDictionary<TBaseKey, TValue>.ContainsKey(TBaseKey key)
{
return key is TKey key2 ? dict.ContainsKey(key2) : false;
}
IEnumerator<KeyValuePair<TBaseKey, TValue>> IEnumerable<KeyValuePair<TBaseKey, TValue>>.GetEnumerator()
{
foreach(var pair in dict)
{
yield return new KeyValuePair<TBaseKey, TValue>(pair.Key, pair.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IReadOnlyDictionary<TBaseKey, TValue>)this).GetEnumerator();
}
bool IReadOnlyDictionary<TBaseKey, TValue>.TryGetValue(TBaseKey key, out TValue value)
{
if(key is TKey key2) return dict.TryGetValue(key2, out value);
value = default;
return false;
}
}
Is there an easy way to add value to a nested dictionary. I am looking for a way to replace the following type of code with an easy one.
if (NestedDictionary.ContainsKey(key1))
{
if (NestedDictionary[key1].ContainsKey(key2))
{
if (NestedDictionary[key1][key2].ContainsKey(key3))
{
//do nothing
}
else
{
NestedDictionary[key1][key2].Add(key3,1);
}
}
else
{
NestedDictionary[key1].Add(key2, new Dictionary<int,int>() { { key3, 1 } });
}
}
else
{
NestedDictionary.Add(key1, new Dictionary<int, Dictionary<int,int>>() { { key2, new Dictionary<int,int>() { { key3, 1} } } });
}
We can write a GetOrAdd method that either gets the value for a particular key if it's there, or assigns a new value if there is none:
public static TValue GetOrAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
TValue newValue)
{
TValue oldValue;
if (dictionary.TryGetValue(key, out oldValue))
return oldValue;
else
{
dictionary.Add(key, newValue);
return newValue;
}
}
(Note you can create a second overload that accepts a Func<TValue> instead of a TValue, which is useful if the value is either expensive to create or causes side effects.)
Now this problem becomes very easy:
var dictionary = new Dictionary<int, Dictionary<int, string>>();
dictionary.GetOrAdd(key1, new Dictionary<int, string>())[key2] = value;
We get the inner dictionary for the outer key, or create a new blank one if it doesn't exist, and then we assign the new value to the dictionary returned. Note that the indexer will add an item if it doesn't exist or update the item if it already does.
This of course scales reasonably well as we add dimensions as well:
var dictionary = new Dictionary<int, Dictionary<int, Dictionary<int, string>>>();
dictionary.GetOrAdd(key1, new Dictionary<int, Dictionary<int, string>>())
.GetOrAdd(key2, new Dictionary<int, string>())[key3] = value;
In our case we are actually fine always adding the default value of TValue using our GetOrAdd method, so if we add an overload to support that:
public static TValue GetOrAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key)
where TValue : new()
{
TValue oldValue;
if (dictionary.TryGetValue(key, out oldValue))
return oldValue;
else
{
var newValue = new TValue();
dictionary.Add(key, newValue);
return newValue;
}
}
It simplifies the code even more:
dictionary.GetOrAdd(key1).GetOrAdd(key2)[key3] = value;
And if you really end up doing this particular operation a lot, you can just create a method to do the whole thing:
public static void AddMany<TKey1, TKey2, TKey3, TValue>(
this Dictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>> dictionary,
TKey1 key1,
TKey2 key2,
TKey3 key3,
TValue newValue)
{
dictionary.GetOrAdd(key1).GetOrAdd(key2)[key3] = newValue;
}
Allowing you to write:
dictionary.AddMany(key1, key2, key3, value);
Of course, you need to create a new AddMany overload for each number of keys you want to support, and it has to be a number known at compile time, but that does appear to be the case in your example.
You can simplify the inner part:
if (NestedDictionary.ContainsKey(key1))
{
if (NestedDictionary[key1].ContainsKey(key2))
{
NestedDictionary[key1][key2][key3]=1;
}
else
{
NestedDictionary[key1].Add(key2, new Dictionary<int,int>() { { key3, 1 } });
}
}
else
{
NestedDictionary.Add(key1, new Dictionary<int, Dictionary<int,int>>() { { key2, new Dictionary<int,int>() { { key3, 1} } } });
}
But that's about it.
But what's the point of the structure? You only ever add a constant value (1) to the innermost dictionary, so there's no real "value" there. You might as well use a List<string> at that level.
I'm working on building a caching service for a website, and I want to provide a public interface that can take a Func<TKey,TValue> or Func<TKey,Task<TValue>> method a that can be called by the service for a cache miss.
I'm ending up duplicating codes in handling these two delegate types. Is there a way to consolidate? I'm especially concerned that 'Func' methods are not thread safe, and are not suitable for wrapping in a 'Task.Run'.
Here's my code:
public interface ICacheServiceEngine
{
Task<CacheResult<TValue>> TryGetValueAsync<TValue>(string key);
Task<CacheResult<TValue>>
TryGetValueAsync<TKey,TValue>(TKey key, Func<TKey,string> keyFunc);
Task<TValue> GetValueAsync<TValue>(string key,
Func<string, TValue> valueSourceFunc);
Task<TValue> GetValueAsync<TKey,TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, TValue> valueSourceFunc);
Task<TValue> GetValueAsync<TValue>(string key,
Func<string, Task<TValue>> valueSourceFuncAsync);
Task<TValue> GetValueAsync<TKey, TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, Task<TValue>> valueSourceFuncAsync);
}
public interface ICacheServiceDataAccessor
{
Task<CacheResult<TValue>> TryGetAsync<TValue>(string key);
Task PutAsync<TValue>(string key , TValue result);
}
public class CacheServiceEngine : ICacheServiceEngine
{
private ICacheServiceDataAccessor cacheDataAccessor;
public CacheServiceEngine(ICacheServiceDataAccessor cacheDataAccessor)
{
// add guard
this.cacheDataAccessor = cacheDataAccessor;
}
public async Task<CacheResult<TValue>> TryGetValueAsync<TValue>(string key)
{
return await this.cacheDataAccessor.TryGetAsync<TValue>(key);
}
public async Task<CacheResult<TValue>> TryGetValueAsync<TKey,TValue>(TKey key,
Func<TKey,string> keyFunc)
{
string keyString = keyFunc(key);
return await this.cacheDataAccessor.TryGetAsync<TValue>(keyString);
}
public async Task<TValue> GetValueAsync<TValue>(string key,
Func<string, TValue> valueSourceFunc)
{
return await this.InnerGetValueAsync(key, () => valueSourceFunc(key));
}
public async Task<TValue> GetValueAsync<TKey,TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, TValue> valueSourceFunc)
{
string keyString = keyFunc(key);
return await this.InnerGetValueAsync(keyString, () => valueSourceFunc(key));
}
public async Task<TValue> GetValueAsync<TValue>(string key,
Func<string, Task<TValue>> valueSourceFuncAsync)
{
return await this.InnerGetValueAsync(key, () => valueSourceFuncAsync(key));
}
public async Task<TValue> GetValueAsync<TKey, TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, Task<TValue>> valueSourceFuncAsync)
{
string keyString = keyFunc(key);
return await this.InnerGetValueAsync(keyString,
() => valueSourceFuncAsync(key));
}
// the two private methods are very close to each other
// can I pull out the similarities, if I assume that 'valueSourceFunc'
// is not thread-safe?
private async Task<TValue> InnerGetValueAsync<TValue>(string key,
Func<TValue> valueSourceFunc)
{
TValue value;
CacheResult<TValue> cacheResult =
await this.cacheDataAccessor.TryGetAsync<TValue>(key);
if (cacheResult.InCache)
{
value = cacheResult.Value;
}
else
{
// this call is normal (synchronous)
value = valueSourceFunc();
await this.cacheDataAccessor.PutAsync(key, value);
}
return value;
}
private async Task<TValue> InnerGetValueAsync<TValue>(string key,
Func<Task<TValue>> valueSourceFuncAsync)
{
TValue value;
CacheResult<TValue> cacheResult =
await this.cacheDataAccessor.TryGetAsync<TValue>(key);
if (cacheResult.InCache)
{
value = cacheResult.Value;
}
else
{
// this call has to be awaited
value = await valueSourceFuncAsync();
await this.cacheDataAccessor.PutAsync(key, value);
}
return value;
}
}
First, you should reconsider your ICacheServiceDataAccessor. It is entirely possible that you may calculate values unnecessarily when a key is not in the cache. I would suggest something like this:
public interface ICacheServiceDataAccessor
{
Task<CacheResult<TValue>> TryGetAsync<TValue>(string key);
Task<CacheResult<TValue>> GetOrPutAsync<TValue>(string key, Func<Task<TValue>> result);
}
But - ignoring that issue for the moment - there is a way to treat a synchronous call as an asynchronous call: Task.FromResult.
public Task<CacheResult<TValue>> TryGetValueAsync<TValue>(string key)
{
return cacheDataAccessor.TryGetAsync<TValue>(key);
}
public Task<CacheResult<TValue>> TryGetValueAsync<TKey,TValue>(TKey key,
Func<TKey, string> keyFunc)
{
string keyString = keyFunc(key);
return cacheDataAccessor.TryGetAsync<TValue>(keyString);
}
public Task<TValue> GetValueAsync<TValue>(string key,
Func<string, TValue> valueSourceFunc)
{
return InnerGetValueAsync(key, () => Task.FromResult(valueSourceFunc(key)));
}
public Task<TValue> GetValueAsync<TKey,TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, TValue> valueSourceFunc)
{
string keyString = keyFunc(key);
return InnerGetValueAsync(keyString, () => Task.FromResult(valueSourceFunc(key)));
}
public Task<TValue> GetValueAsync<TValue>(string key,
Func<string, Task<TValue>> valueSourceFuncAsync)
{
return InnerGetValueAsync(key, () => valueSourceFuncAsync(key));
}
public async Task<TValue> GetValueAsync<TKey, TValue>(TKey key,
Func<TKey,string> keyFunc, Func<TKey, Task<TValue>> valueSourceFuncAsync)
{
string keyString = keyFunc(key);
return InnerGetValueAsync(keyString, () => valueSourceFuncAsync(key));
}
As a final design note, I would consider having only the most generic of these be an actual member of ICacheServiceEngine. Since the others are actually just overloads for that method (and will always have the same implementation regardless of the derived class), they could be defined as extension methods on ICacheServiceEngine.
I often find myself creating a Dictionary with a non-trivial value class (e.g. List), and then always writing the same code pattern when filling in data.
For example:
var dict = new Dictionary<string, List<string>>();
string key = "foo";
string aValueForKey = "bar";
That is, I want to insert "bar" into the list that corresponds to key "foo", where key "foo" might not be mapped to anything.
This is where I use the ever-repeating pattern:
List<string> keyValues;
if (!dict.TryGetValue(key, out keyValues))
dict.Add(key, keyValues = new List<string>());
keyValues.Add(aValueForKey);
Is there a more elegant way of doing this?
Related questions that don't have answers to this question:
Is there an IDictionary implementation that returns null on missing key instead of throwing?
Find-or-insert with only one lookup in c# dictionary
Dictionary returning a default value if the key does not exist
We have a slightly different take on this, but the effect is similar:
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
where TValue : new()
{
if (!dict.TryGetValue(key, out TValue val))
{
val = new TValue();
dict.Add(key, val);
}
return val;
}
Called:
var dictionary = new Dictionary<string, List<int>>();
List<int> numbers = dictionary.GetOrCreate("key");
It makes use of the generic constraint for public parameterless constructors: where TValue : new().
To help with discovery, unless the extension method is quite specific to a narrow problem, we tend to place extension methods in the namespace of the type they are extending, in this case:
namespace System.Collections.Generic
Most of the time, the person using the type has the using statement defined at the top, so IntelliSense would also find the extension methods for it defined in your code.
As with so many programming problems, when you find yourself doing something a lot, refactor it into a method:
public static void MyAdd<TKey, TCollection, TValue>(
this Dictionary<TKey, TCollection> dictionary, TKey key, TValue value)
where TCollection : ICollection<TValue>, new()
{
TCollection collection;
if (!dictionary.TryGetValue(key, out collection))
{
collection = new TCollection();
dictionary.Add(key, collection);
}
collection.Add(value);
}
If you use .Net Core you can use Dictionary<>.TryAdd().
var dict = new Dictionary<string, string>();
dict.TryAdd("foo", "bar"); // returns bool whether it added or not feel free to ignore.
var myValue = dict["foo"];
Here is a solution in case the constructor requires a parameter.
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> createNew)
{
if (!dict.TryGetValue(key, out var val))
{
val = createNew();
dict.Add(key, val);
}
return val;
}
Simple to use:
MyDict.GetOrCreate(si.Id, createNew: () => new ObjectKnowingItsId(si.Id))
For further readers, here are some extensions in every flavour I thought fit. You could also do something with an out parameter if you need to check if you have added a value but i think you can use containskey or something already for that.
You can use GetOrAddNew to retrieve an item, or create and add it to the dict. You can use the various overloads of GetOrAdd to add a new value. This could be the default so e.g. NULL or 0 but you can also provide a lambda to construct an object for you, with any kind of constructor arguments you'd like.
var x = new Dictionary<string, int>();
var val = x.GetOrAdd("MyKey", (dict, key) => dict.Count + 2);
var val2 = x.GetOrAdd("MyKey", () => Convert.ToInt32("2"));
var val3 = x.GetOrAdd("MyKey", 1);
/// <summary>
/// Extensions for dealing with <see cref="Dictionary{TKey,TValue}"/>
/// </summary>
public static class DictionaryExtensions
{
public static TValue GetOrAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
where TValue : new()
=> dict.GetOrAdd(key, (values, innerKey) => EqualityComparer<TValue>.Default.Equals(default(TValue), defaultValue) ? new TValue() : defaultValue);
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
=> dict.GetOrAdd(key, (values, innerKey) => defaultValue);
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> valueProvider)
=> dict.GetOrAdd(key, (values, innerKey) => valueProvider());
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueProvider)
=> dict.GetOrAdd(key, (values, innerKey) => valueProvider(key));
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<IDictionary<TKey, TValue>, TKey, TValue> valueProvider)
{
if (dict == null) throw new ArgumentNullException(nameof(dict));
if (key == null) throw new ArgumentNullException(nameof(key));
if (valueProvider == null) throw new ArgumentNullException(nameof(valueProvider));
if (dict.TryGetValue(key, out var foundValue))
return foundValue;
dict[key] = valueProvider(dict, key);
return dict[key];
}
}
And what about this?
var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);
Slight twist
Had a special need that was a literal match to the question, but maybe not its intent. For this case, getting the value was expensive (reflection) and only wanted to generate the value once, to populate the dictionary for caching. Building off #adam-houldsworth answer, the value argument was modified into a delegate.
public static TValue GetOrCreate<TKey, TValue>(
this IDictionary<TKey, TValue> self,
TKey key,
Func<TValue> getValue)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
else if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
else if (!self.ContainsKey(key))
{
self[key] = getValue() ;
}
return self[key];
}
Usage
var assemblyName = callingAssemblies.GetOrCreate
(
path,
() => Assembly.GetCallingAssembly().GetName().Name
);
Ok, different approach:
public static bool TryAddValue<TKey,TValue>(this System.Collections.Generic.IDictionary<TKey,List<TValue>> dictionary, TKey key, TValue value)
{
// Null check (useful or not, depending on your null checking approach)
if (value == null)
return false;
List<TValue> tempValue = default(List<TValue>);
try
{
if (!dictionary.TryGetValue(key, out tempValue))
{
dictionary.Add(key, tempValue = new List<TValue>());
}
else
{
// Double null check (useful or not, depending on your null checking approach)
if (tempValue == null)
{
dictionary[key] = (tempValue = new List<TValue>());
}
}
tempValue.Add(value);
return true;
}
catch
{
return false;
}
}
In this way you have to "try to add" your value to a generic List of (obviously generalizable to a generic collection), null checking and trying to get existing key/values in your Dictionary.
Usage and example:
var x = new Dictionary<string,List<string>>();
x.TryAddValue("test", null); // return false due to null value. Doesn't add the key
x.TryAddValue("test", "ok"); // it works adding the key/value
x.TryAddValue("test", "ok again"); // it works adding the value to the existing list
Hope it helps.
I am missing the GetOrAdd for Dictionary, that does exist for ConcurrentDictionary.
ConcurrentDictionary<int,Guid> Conversion = new ConcurrentDictionary<int, Guid>();
List<int> before = new List<int> { 1, 2, 1, 3 };
List<Guid> after = before.Select(x => Conversion.GetOrAdd(x, Guid.NewGuid())).ToList();
This code will generate Guids for each number. Ending up converting both 1's in before to the same Guid.
In your case:
ConcurrentDictionary<int, List<String>> dict;
keyValues = dict.GetOrAdd(key, new List<String>());
keyValues.Add(aValueForKey);
using System.Runtime.InteropServices;
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueProvider)
where TKey: notnull
{
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
if (!exists) value = valueProvider.Invoke();
return value!;
}
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
where TKey: notnull
=> GetOrCreate(dictionary, key, () => value);
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TKey: notnull
where TValue : new()
=> GetOrCreate(dictionary, key, new TValue());
To avoid duplicate hash lookups.
You can find more info here.
ConcurrentDictionary.GetOrAdd does exactly what is being asked.
Initialization
ConcurrentDictionary<string, List<string>> dict = new();
Usage
var list = dict.GetOrAdd(key, _ => new List<string>());
Notice how we are using a factory method to create a new list as-need. This prevents pre-allocating a list on each call to GetOrAdd.
Building on this question, is there a simple solution for having a multi-key dictionary where either key individually can be used to identify the value?
ie.
MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned
This blog post seems to detail a rather decent implementation.
Multi-key generic dictionary class for C#
MultiKeyDictionary is a C# class
that wraps and extends the Generic
Dictionary object provided by
Microsoft in .NET 2.0 and above. This
allows a developer to create a generic
dictionary of values and reference the
value list through two keys instead of
just the one provided by the Microsoft
implementation of the Generic
Dictionary<...>. You can see my
article on CodeProject (here), however
this code is more up-to-date and bug
free.
Yes, define a class that adds the object to an internal hashtable with both keys,
public MyClass<k1, k2, T>: Dictionary<object, T>
{
private Dictionary<k1, k2> keyMap;
public new Add(k1 key1Val, k2 key2Val, T object)
{
keyMap.Add(key1Val, key2Val);
base.Add(k2, object)
}
public Remove(k1 key1Val)
{
base.Remove(keyMap[key1Val]);
keyMap.Remove(key1Val);
}
public Remove(k2 key2Val)
{
base.Remove(key2Val);
keyMap.Remove(key2Val);
}
}
There's nothing built into .NET BCL for this type of collection at the moment.
I see two options:
Use a two-level dictionary. The first level maps different keys to some common unique key (let's say a GUID), and the second level maps the GUID to the actual value.
Create a custom key class and implement Equals() and GetHashCode() so that any one component of the key is sufficient to find the entire key. You could then supply helper methods to construct instances of the key using only one of the values so that you could do lookups.
Another simple (and effective) implementation would be to use PowerCollections' Pair<TFirst, TSecond> type as a dictionary key, something like
Dictionary<Pair<TKey1, TKey2>, TValue> foo;
foo.Add(new Pair<TKey1, TKey2>(key1, key2), value);
Pair<> implements Equals and GetHashCode consistently, so you don't need to resort to multi-level dictionaries (which are more cumbersome and probably less effective).
There's also a Triple<TFirst, TSecond, TThird> if you need a 3-key dictionary.
I find many answers here unnecessarily complex, less performant or plain unusable. The best approach would be to have a KeyValuePair<> of the secondary key and the value clubbed together as the Value of either dictionaries. This lets you have just one lookup for for removal and updation operations. A straightforward implementation:
public class DualDictionary<TKey1, TKey2, TValue> : IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>>
{
Dictionary<TKey1, KeyValuePair<TKey2, TValue>> _firstKeys;
Dictionary<TKey2, KeyValuePair<TKey1, TValue>> _secondKeys;
public int Count
{
get
{
if (_firstKeys.Count != _secondKeys.Count)
throw new Exception("somewhere logic went wrong and your data got corrupt");
return _firstKeys.Count;
}
}
public ICollection<TKey1> Key1s
{
get { return _firstKeys.Keys; }
}
public ICollection<TKey2> Key2s
{
get { return _secondKeys.Keys; }
}
public IEnumerable<TValue> Values
{
get { return this.Select(kvp => kvp.Value); }
}
public DualDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
{
_firstKeys = new Dictionary<TKey1, KeyValuePair<TKey2, TValue>>(comparer1);
_secondKeys = new Dictionary<TKey2, KeyValuePair<TKey1, TValue>>(comparer2);
}
public bool ContainsKey1(TKey1 key)
{
return ContainsKey(key, _firstKeys);
}
private static bool ContainsKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
{
return dict.ContainsKey(key);
}
public bool ContainsKey2(TKey2 key)
{
return ContainsKey(key, _secondKeys);
}
public TValue GetValueByKey1(TKey1 key)
{
return GetValueByKey(key, _firstKeys);
}
private static TValue GetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
{
return dict[key].Value;
}
public TValue GetValueByKey2(TKey2 key)
{
return GetValueByKey(key, _secondKeys);
}
public bool TryGetValueByKey1(TKey1 key, out TValue value)
{
return TryGetValueByKey(key, _firstKeys, out value);
}
private static bool TryGetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, out TValue value)
{
KeyValuePair<T, TValue> otherPairing;
bool b = TryGetValue(key, dict, out otherPairing);
value = otherPairing.Value;
return b;
}
private static bool TryGetValue<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict,
out KeyValuePair<T, TValue> otherPairing)
{
return dict.TryGetValue(key, out otherPairing);
}
public bool TryGetValueByKey2(TKey2 key, out TValue value)
{
return TryGetValueByKey(key, _secondKeys, out value);
}
public bool Add(TKey1 key1, TKey2 key2, TValue value)
{
if (ContainsKey1(key1) || ContainsKey2(key2)) // very important
return false;
AddOrUpdate(key1, key2, value);
return true;
}
// dont make this public; a dangerous method used cautiously in this class
private void AddOrUpdate(TKey1 key1, TKey2 key2, TValue value)
{
_firstKeys[key1] = new KeyValuePair<TKey2, TValue>(key2, value);
_secondKeys[key2] = new KeyValuePair<TKey1, TValue>(key1, value);
}
public bool UpdateKey1(TKey1 oldKey, TKey1 newKey)
{
return UpdateKey(oldKey, _firstKeys, newKey, (key1, key2, value) => AddOrUpdate(key1, key2, value));
}
private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, KeyValuePair<T, TValue>> dict, S newKey,
Action<S, T, TValue> updater)
{
KeyValuePair<T, TValue> otherPairing;
if (!TryGetValue(oldKey, dict, out otherPairing) || ContainsKey(newKey, dict))
return false;
Remove(oldKey, dict);
updater(newKey, otherPairing.Key, otherPairing.Value);
return true;
}
public bool UpdateKey2(TKey2 oldKey, TKey2 newKey)
{
return UpdateKey(oldKey, _secondKeys, newKey, (key1, key2, value) => AddOrUpdate(key2, key1, value));
}
public bool UpdateByKey1(TKey1 key, TValue value)
{
return UpdateByKey(key, _firstKeys, (key1, key2) => AddOrUpdate(key1, key2, value));
}
private static bool UpdateByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, Action<S, T> updater)
{
KeyValuePair<T, TValue> otherPairing;
if (!TryGetValue(key, dict, out otherPairing))
return false;
updater(key, otherPairing.Key);
return true;
}
public bool UpdateByKey2(TKey2 key, TValue value)
{
return UpdateByKey(key, _secondKeys, (key1, key2) => AddOrUpdate(key2, key1, value));
}
public bool RemoveByKey1(TKey1 key)
{
return RemoveByKey(key, _firstKeys, _secondKeys);
}
private static bool RemoveByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> keyDict,
Dictionary<T, KeyValuePair<S, TValue>> valueDict)
{
KeyValuePair<T, TValue> otherPairing;
if (!TryGetValue(key, keyDict, out otherPairing))
return false;
if (!Remove(key, keyDict) || !Remove(otherPairing.Key, valueDict))
throw new Exception("somewhere logic went wrong and your data got corrupt");
return true;
}
private static bool Remove<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
{
return dict.Remove(key);
}
public bool RemoveByKey2(TKey2 key)
{
return RemoveByKey(key, _secondKeys, _firstKeys);
}
public void Clear()
{
_firstKeys.Clear();
_secondKeys.Clear();
}
public IEnumerator<KeyValuePair<Tuple<TKey1, TKey2>, TValue>> GetEnumerator()
{
if (_firstKeys.Count != _secondKeys.Count)
throw new Exception("somewhere logic went wrong and your data got corrupt");
return _firstKeys.Select(kvp => new KeyValuePair<Tuple<TKey1, TKey2>, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key),
kvp.Value.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Few things to note:
I have implemented only IEnumerable<>. I don't think ICollection<> makes sense here since the method names all could be way different for this special collection structure. Up to you to decide what should go inside IEnumerable<>.
I have attempted for some weird exceptions to be thrown here and there - just for data integrity. Just to be on the safer side so that you know if ever my code has bugs.
I have named methods in such a way that its compilable even when Key1 and Key2 are of the same type.
Performance: You can lookup for Value with either of the Keys. Get and Contains method require just 1 lookup (O(1)). Add requires 2 lookups and 2 adds. Update requires 1 lookup and 2 adds. Remove takes 3 lookups.
I tried this and it works perfectly (include add, remove & indexer)
public class MultikeyDictionary<K1, K2, V> : Dictionary<KeyValuePair<K1, K2>, V>
{
public V this[K1 index1, K2 index2]
{
get
{
return this[new KeyValuePair<K1, K2>(index1, index2)];
}
set
{
this[new KeyValuePair<K1, K2>(index1, index2)] = value;
}
}
public bool Remove(K1 index1, K2 index2)
{
return base.Remove(new KeyValuePair<K1,K2>(index1, index2));
}
public void Add(K1 index1, K2 index2, V value)
{
base.Add(new KeyValuePair<K1, K2>(index1, index2), value);
}
}
and even I extended it to 4 values:
public class MultikeyDictionary<K1, K2, K3, V> : MultikeyDictionary<KeyValuePair<K1, K2>, K3, V>
{
public V this[K1 index1, K2 index2, K3 index3]
{
get
{
return base[new KeyValuePair<K1, K2>(index1, index2), index3];
}
set
{
base[new KeyValuePair<K1, K2>(index1, index2), index3] = value;
}
}
public bool Remove(K1 index1, K2 index2, K3 index3)
{
return base.Remove(new KeyValuePair<K1, K2>(index1, index2), index3);
}
public void Add(K1 index1, K2 index2, K3 index3, V value)
{
base.Add(new KeyValuePair<K1, K2>(index1, index2), index3, value);
}
}
Enjoy,
Ofir
Sure, it's an OO language and you can implement whatever O's you want. You are going to have some ambiguity to resolve (what if TKey1 and TKey2 are the same type, which methods get called then?)
You won't be able to define the overloads for both types, and the generics system doesn't allow for an arbitrary number of types (like methods allow params). So, you'd be stuck with a set of classes which defined 2, 3, 4, etc. simultaneous keys. Additionally, you'd have to use object as the parameter for get and set, using runtime type checks to simulate the overload.
Additionally, you'd only store one dictionary of <TKEY1,VAL>, the other dictionaries would be of <TKEY2,TKEY1>, <TKEY3,TKEY1> and would act as indexes on the main dictionary.
It's mostly boiler plate code.
You may find my IndexMap implementation to be a good base for rewriting it from Java into C#. The programming model isn't as elegant as I'd prefer, but it isn't meant for developing with directly. Rather it lies behind a caching library which supplies standard annotations to allow for a succinct coding style. By using the Map interface it provides a clean compositional model when combining it with self-populating, expirational, and evictible map decorators. I am sure that someone could come up with a nice programming interface for direct usage where it is acceptable to lose the benefit of the Map interface.
Not a direct solution and not viable for multi keys, but worked for my use case.
Dictionary<Guid, Object> objs = new Dictionary<Guid, Object>();
Dictionary<int, Guid> guids = new Dictionary<int, Guid>();
private void Set(object sender, Object obj)
{
objs[obj.Guid] = obj;
guids[obj.Id] = obj.Guid;
}
public Object Get(int id)
{
return guids.ContainsKey(id) ? Get(guids[id]) : null;
}
public Object Get(Guid guid)
{
return objs.ContainsKey(guid) ? objs[guid] : null;
}
I wrote a MultiKeyDictionary package for net472, net481, netstandard2.1, and net6.0.
In this version, however, you can combine key1 with arbitrary key2s. You can slice, contains, add, remove, clear, set, index, trygetvalue, tryslice, and enumerate. There's a maximum of 5 keys supported.