C# cast Dictionary to Dictionary (Involving Reflection)

前端 未结 8 1065
无人及你
无人及你 2021-01-04 10:04

Is it possible to cast a Dictionary to a consistent intermediate generic type? So I would be able to cast <

8条回答
  •  太阳男子
    2021-01-04 10:36

    Explanation of What's Wrong: Variance

    As stated in this answer:

    it's not true that a Dictionary is a Dictionary

    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.

    A Type-Safe Partial Solution Using Variant Generics

    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.

提交回复
热议问题