C# cast Dictionary to Dictionary (Involving Reflection)

前端 未结 8 979
无人及你
无人及你 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:31

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-04 10:36

    Explanation of What's Wrong: Variance

    As stated in this answer:

    it's not true that a Dictionary<string, bool> is a Dictionary<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.

    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<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.

    0 讨论(0)
提交回复
热议问题