Is it possible to cast a Dictionary to a consistent intermediate generic type? So I would be able to cast
As stated in this answer:
it's not true that a
Dictionaryis aDictionary
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 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 : IDictionaryBase, IInKeySemiDictionary
{
V this[K key] { set; }
void Add(K key, V value);
}
public interface IInKeyOutValueSemiDictionary : IDictionaryBase, IInKeySemiDictionary, IOutValueSemiDictionary
{
V this[K key] { get; }
ISuccessTuple TryGetValue(K key);
}
public interface ISuccessTuple
{
bool WasSuccessful { get; }
V Value { get; }
}
public class SuccessTupleImpl : ISuccessTuple
{
public bool WasSuccessful { get; }
public V Value {get;}
public SuccessTupleImpl(bool wasSuccessful, V value)
{
WasSuccessful = wasSuccessful;
Value = value;
}
}
public interface IInKeySemiDictionary : IDictionaryBase
{
bool ContainsKey(K key);
bool Remove(K key);
}
public interface IOutKeySemiDictionary : IDictionaryBase
{
IEnumerable Keys { get; }
}
public interface IOutValueSemiDictionary : IDictionaryBase
{
IEnumerable 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 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 : IInKeyInValueSemiDictionary, IInKeyOutValueSemiDictionary,
IOutKeySemiDictionary, IDictionary
{ }
And then, the wrapper class:
class VariantDictionaryImpl : IVariantDictionary
{
private readonly IDictionary _backingDictionary;
public VariantDictionaryImpl(IDictionary backingDictionary)
{
_backingDictionary = backingDictionary ?? throw new ArgumentNullException(nameof(backingDictionary));
}
public V this[K key] { set => _backingDictionary[key] = value; }
V IInKeyOutValueSemiDictionary.this[K key] => _backingDictionary[key];
V IDictionary.this[K key] { get => _backingDictionary[key]; set => _backingDictionary[key] = value; }
public int Count => _backingDictionary.Count;
public bool IsReadOnly => _backingDictionary.IsReadOnly;
public IEnumerable Keys => _backingDictionary.Keys;
public ICollection Values => _backingDictionary.Values;
ICollection IDictionary.Keys => _backingDictionary.Keys;
IEnumerable IOutValueSemiDictionary.Values => Values;
public void Add(K key, V value)
{
_backingDictionary.Add(key, value);
}
public void Add(KeyValuePair item)
{
_backingDictionary.Add(item);
}
public void Clear()
{
_backingDictionary.Clear();
}
public bool Contains(KeyValuePair item)
{
return _backingDictionary.Contains(item);
}
public bool ContainsKey(K key)
{
return _backingDictionary.ContainsKey(key);
}
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
_backingDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator> GetEnumerator()
{
return _backingDictionary.GetEnumerator();
}
public bool Remove(K key)
{
return _backingDictionary.Remove(key);
}
public bool Remove(KeyValuePair item)
{
return _backingDictionary.Remove(item);
}
public ISuccessTuple TryGetValue(K key)
{
bool wasSuccessful = _backingDictionary.TryGetValue(key, out V v);
return new SuccessTupleImpl(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.