Is it possible to cast a Dictionary
to a consistent intermediate generic type? So I would be able to cast
I had the same situation and created a WrapperDictionary class and a cast extension method. Found it helpful if you also want to make operations with it like remove a item.
Example:
const string TestKey1 = "Key";
const string TestValue1 = "Value";
var dict = new Dictionary<string, string>();
dict.Add(TestKey1, TestValue1);
var wrapper = dict.CastDictionary<string, string, string, object>();
wrapper.Remove(TestKey1);
Code:
public class DictionaryWrapper<TKeyTarget, TValueTarget, TKeySource, TValueSource> : IDictionary<TKeyTarget, TValueTarget>
{
#region types
private class EnumeratorWrapper : IEnumerator<KeyValuePair<TKeyTarget, TValueTarget>>
{
private readonly IEnumerator<KeyValuePair<TKeySource, TValueSource>> _enumerator;
private readonly DictionaryWrapper<TKeyTarget, TValueTarget, TKeySource, TValueSource> _dictionaryWrapper;
public EnumeratorWrapper(IEnumerator<KeyValuePair<TKeySource, TValueSource>> enumerator, DictionaryWrapper<TKeyTarget, TValueTarget, TKeySource, TValueSource> dictionaryWrapper)
{
_enumerator = enumerator;
_dictionaryWrapper = dictionaryWrapper;
}
public void Dispose()
{
_enumerator.Dispose();
}
public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset()
{
_enumerator.Reset();
}
public KeyValuePair<TKeyTarget, TValueTarget> Current => _dictionaryWrapper._kvpSourceToTargetFunc(_enumerator.Current);
object IEnumerator.Current => Current;
}
#endregion
#region fields
private readonly IDictionary<TKeySource, TValueSource> _dictionary;
private readonly Func<TKeySource, TKeyTarget> _keySourceToTargetFunc;
private readonly Func<TKeyTarget, TKeySource> _keyTargetToSourceFunc;
private readonly Func<TValueSource, TValueTarget> _valueSourceToTargetFunc;
private readonly Func<TValueTarget, TValueSource> _valueTargetToSourceFunc;
private readonly Func<KeyValuePair<TKeySource, TValueSource>, KeyValuePair<TKeyTarget, TValueTarget>> _kvpSourceToTargetFunc;
private readonly Func<KeyValuePair<TKeyTarget, TValueTarget>, KeyValuePair<TKeySource, TValueSource>> _kvpTargetToSourceFunc;
#endregion
#region Construction
public DictionaryWrapper(
IDictionary<TKeySource, TValueSource> dict,
Func<TKeySource, TKeyTarget> keySourceToTargetFunc = null,
Func<TKeyTarget, TKeySource> keyTargetToSourceFunc = null,
Func<TValueSource, TValueTarget> valueSourceToTargetFunc = null,
Func<TValueTarget, TValueSource> valueTargetToSourceFunc = null)
{
_dictionary = dict;
_keySourceToTargetFunc = keySourceToTargetFunc ?? (i => (TKeyTarget) (object) i);
_keyTargetToSourceFunc = keyTargetToSourceFunc ?? (i => (TKeySource) (object) i);
_valueSourceToTargetFunc = valueSourceToTargetFunc ?? (i => (TValueTarget) (object) i);
_valueTargetToSourceFunc = valueTargetToSourceFunc ?? (i => (TValueSource) (object) i);
_kvpSourceToTargetFunc =
kvp => new KeyValuePair<TKeyTarget, TValueTarget>(_keySourceToTargetFunc(kvp.Key), _valueSourceToTargetFunc(kvp.Value));
_kvpTargetToSourceFunc =
kvp => new KeyValuePair<TKeySource, TValueSource>(_keyTargetToSourceFunc(kvp.Key), _valueTargetToSourceFunc(kvp.Value));
}
#endregion
#region Interface Members
public IEnumerator<KeyValuePair<TKeyTarget, TValueTarget>> GetEnumerator()
{
return new EnumeratorWrapper(_dictionary.GetEnumerator(), this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(KeyValuePair<TKeyTarget, TValueTarget> item)
{
_dictionary.Add(_kvpTargetToSourceFunc(item));
}
public void Clear()
{
_dictionary.Clear();
}
public bool Contains(KeyValuePair<TKeyTarget, TValueTarget> item)
{
return _dictionary.Contains(_kvpTargetToSourceFunc(item));
}
public void CopyTo(KeyValuePair<TKeyTarget, TValueTarget>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<TKeyTarget, TValueTarget> item)
{
return _dictionary.Remove(_kvpTargetToSourceFunc(item));
}
public int Count => _dictionary.Count;
public bool IsReadOnly => _dictionary.IsReadOnly;
public bool ContainsKey(TKeyTarget key)
{
return _dictionary.ContainsKey(_keyTargetToSourceFunc(key));
}
public void Add(TKeyTarget key, TValueTarget value)
{
_dictionary.Add(_keyTargetToSourceFunc(key), _valueTargetToSourceFunc(value));
}
public bool Remove(TKeyTarget key)
{
return _dictionary.Remove(_keyTargetToSourceFunc(key));
}
public bool TryGetValue(TKeyTarget key, out TValueTarget value)
{
var success = _dictionary.TryGetValue(_keyTargetToSourceFunc(key), out TValueSource result);
value = success ? _valueSourceToTargetFunc(result) : default;
return success;
}
public TValueTarget this[TKeyTarget key]
{
get => _valueSourceToTargetFunc(_dictionary[_keyTargetToSourceFunc(key)]);
set => _dictionary[_keyTargetToSourceFunc(key)] = _valueTargetToSourceFunc(value);
}
public ICollection<TKeyTarget> Keys => _dictionary.Keys.Select(k => _keySourceToTargetFunc(k)).ToList();
public ICollection<TValueTarget> Values => _dictionary.Values.Select(v => _valueSourceToTargetFunc(v)).ToList();
#endregion
}
public static class DictionaryWrapperExtensions
{
public static IDictionary<TKeyCasted, TValueCasted> CastDictionary<TKey, TValue, TKeyCasted, TValueCasted>(this IDictionary<TKey, TValue> dictionary)
{
return new DictionaryWrapper<TKeyCasted,TValueCasted,TKey,TValue>(dictionary);
}
}
As stated in this answer:
it's not true that a
Dictionary<string, bool>
is aDictionary<string,object>
Let me explain why. It all has to do with covariance and contravariance in C#. In summary, neither the key type K
nor the value type V
in Dictionary<K, V>
is either purely an input parameter or an output parameter across the entire dictionary. So neither generic type can be cast to a weaker or a stronger type.
If you cast to a weaker type, then you break inputs. For example the Add
function which expects types K, V
or stronger cannot accept supertypes of either of K, V
. If you cast to a stronger type, you break outputs. For example, the indexer property returns a type V
. How can we cast this safely to a sub-type of V
, knowing only that the original type is V
? We can't.
There is a way to allow strongly-typed casting, but only on partial fragments of the original dictionary interface which are consistent in terms of covariance/contravariance of generic parameters. This solution uses a wrapper type which implements a bunch of partial interfaces. Each partial interface has a specific type of combination of co/contra-variance of the key/value type. Then we combine all these partial interfaces into a master interface and implement that through a backing object which is a regular dictionary. Here goes. First, the interfaces:
public interface IDictionaryBase
{
int Count { get; }
bool IsReadOnly { get; }
void Clear();
}
public interface IInKeyInValueSemiDictionary<in K, in V> : IDictionaryBase, IInKeySemiDictionary<K>
{
V this[K key] { set; }
void Add(K key, V value);
}
public interface IInKeyOutValueSemiDictionary<in K, out V> : IDictionaryBase, IInKeySemiDictionary<K>, IOutValueSemiDictionary<V>
{
V this[K key] { get; }
ISuccessTuple<V> TryGetValue(K key);
}
public interface ISuccessTuple<out V>
{
bool WasSuccessful { get; }
V Value { get; }
}
public class SuccessTupleImpl<V> : ISuccessTuple<V>
{
public bool WasSuccessful { get; }
public V Value {get;}
public SuccessTupleImpl(bool wasSuccessful, V value)
{
WasSuccessful = wasSuccessful;
Value = value;
}
}
public interface IInKeySemiDictionary<in K> : IDictionaryBase
{
bool ContainsKey(K key);
bool Remove(K key);
}
public interface IOutKeySemiDictionary<out K> : IDictionaryBase
{
IEnumerable<K> Keys { get; }
}
public interface IOutValueSemiDictionary<out V> : IDictionaryBase
{
IEnumerable<V> Values { get; }
}
Note: we don't have to cover all combinations of in/out here for the generic parameters, and also note that some of the interfaces only need a single generic parameter.The reason is some combinations of covariance/contravariance don't have any associated methods, so there's not need for corresponding types. And note that I'm leaving out the KeyValuePair<K, V>
type- you'd need to do a similar trick on this if you wanted to include it, and it doesn't seem worth it at this stage.
So, next, the union of all those interfaces:
public interface IVariantDictionary<K, V> : IInKeyInValueSemiDictionary<K, V>, IInKeyOutValueSemiDictionary<K, V>,
IOutKeySemiDictionary<K>, IDictionary<K, V>
{ }
And then, the wrapper class:
class VariantDictionaryImpl<K, V> : IVariantDictionary<K, V>
{
private readonly IDictionary<K, V> _backingDictionary;
public VariantDictionaryImpl(IDictionary<K, V> backingDictionary)
{
_backingDictionary = backingDictionary ?? throw new ArgumentNullException(nameof(backingDictionary));
}
public V this[K key] { set => _backingDictionary[key] = value; }
V IInKeyOutValueSemiDictionary<K, V>.this[K key] => _backingDictionary[key];
V IDictionary<K, V>.this[K key] { get => _backingDictionary[key]; set => _backingDictionary[key] = value; }
public int Count => _backingDictionary.Count;
public bool IsReadOnly => _backingDictionary.IsReadOnly;
public IEnumerable<K> Keys => _backingDictionary.Keys;
public ICollection<V> Values => _backingDictionary.Values;
ICollection<K> IDictionary<K, V>.Keys => _backingDictionary.Keys;
IEnumerable<V> IOutValueSemiDictionary<V>.Values => Values;
public void Add(K key, V value)
{
_backingDictionary.Add(key, value);
}
public void Add(KeyValuePair<K, V> item)
{
_backingDictionary.Add(item);
}
public void Clear()
{
_backingDictionary.Clear();
}
public bool Contains(KeyValuePair<K, V> item)
{
return _backingDictionary.Contains(item);
}
public bool ContainsKey(K key)
{
return _backingDictionary.ContainsKey(key);
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
_backingDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
return _backingDictionary.GetEnumerator();
}
public bool Remove(K key)
{
return _backingDictionary.Remove(key);
}
public bool Remove(KeyValuePair<K, V> item)
{
return _backingDictionary.Remove(item);
}
public ISuccessTuple<V> TryGetValue(K key)
{
bool wasSuccessful = _backingDictionary.TryGetValue(key, out V v);
return new SuccessTupleImpl<V>(wasSuccessful, v);
}
public bool TryGetValue(K key, out V value)
{
return _backingDictionary.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_backingDictionary).GetEnumerator();
}
}
Once you wrap a dictionary in this class, you can then use it either as a regular dictionary, or as any of the covariant/contravariant fragment interface types defined.