In C#: How to declare a generic Dictionary with a type as key and an IEnumerable<> of that type as value?

后端 未结 6 1268
我在风中等你
我在风中等你 2020-12-24 12:10

I want to declare a dictionary that stores typed IEnumerable\'s of a specific type, with that exact type as key, like so: (Edited to follow johny g\'s comment)<

相关标签:
6条回答
  • 2020-12-24 12:36
    1. You can't constrain a specific variable. It only works on classes and methods. It really doesn't make any sense in the variable level, to be honest.
    2. What you want is a custom class - class WeirdDictionary : IDictionary<Type, IEnumerable>, that will overload the Add method to take a Type and an IEnumerable of that type, which you can do using constraints, and cast the IEnumerable<> to IEnumerable. Overload the indexer aswell, and cast it back to the relevant type.
      All this casting is needed, since generics are strict about IEnumerable<Base> being as good as IEnumerable<Derived> (This is called variance, I believe?)

    This solution is slightly generalized, since reuse rocks

    Edit by 280Z28:

    At first I marked this down because point #2 was confusing and I misinterpreted it. By using explicit implementation of methods in IDictionary<Type, IEnumerable> and providing generic alternatives, you can get a pretty clean interface. Note that you cannot create generic indexers, so you'll have to always use TryGet<T> (which is a good idea anyway). I only included explicit implementation of one of the IDictionary<> methods to show how to perform the checks. Do not derive WeirdDictionary directly from Dictionary<Type, IEnumerable> or you will lose the ability to guarantee constraints in the underlying data.

    class WeirdDictionary : IDictionary<Type, IEnumerable>
    {
        private readonly Dictionary<Type, IEnumerable> _data =
            new Dictionary<Type, IEnumerable>();
    
        public void Add<T>(IEnumerable<T> value)
        {
            _data.Add(typeof(T), value);
        }
    
        public bool TryGet<T>(out IEnumerable<T> value)
        {
            IEnumerable enumerable;
            if (_data.TryGetValue(typeof(T), out enumerable)
            {
                value = (IEnumerable<T>)enumerable;
                return true;
            }
    
            value = null;
            return false;
        }
    
        // use explicit implementation to discourage use of this method since
        // the manual type checking is much slower that the generic version above
        void IDictionary<Type, IEnumerable>.Add(Type key, IEnumerable value)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            if (value != null && !typeof(IEnumerable<>).MakeGenericType(key).IsAssignableFrom(value.GetType()))
                throw new ArgumentException(string.Format("'value' does not implement IEnumerable<{0}>", key));
    
            _data.Add(key, value);
        }
    }
    

    End 280Z28

    0 讨论(0)
  • Use System.ComponentModel.Design.ServiceContainer that is already available in .Net framework.

            ServiceContainer container = new ServiceContainer();
    
            IList<int> integers = new List<int>();
            IList<string> strings = new List<string>();
            IList<double> doubles = new List<double>();
    
            container.AddService(typeof(IEnumerable<int>), integers);
            container.AddService(typeof(IEnumerable<string>), strings);
            container.AddService(typeof(IEnumerable<double>), doubles);
    
    0 讨论(0)
  • 2020-12-24 12:40

    Try this:

    public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>>  { } 
    
    0 讨论(0)
  • 2020-12-24 12:41

    Make a custom Dictionary class:

    public class BaseClassDictionary<T, IEnumerable<T>> : Dictionary<T, IEnumerable<T>>
        where T : BaseClass
    {
    }
    

    Then you can use this specialized dictionary instead as field type:

    private BaseClassDictionary<BaseClassDerivedType, IEnumerable<BaseClassDerivedType>> myDictionary;
    
    0 讨论(0)
  • 2020-12-24 12:48

    You may not even need a dictionary to be able to do this - but that depends on your needs. If you only ever need 1 such list per type per appdomain (i.e. the "dictionary" is static), the following pattern can be efficient and promotes type-inference nicely:

    interface IBase {}
    
    static class Container {
        static class PerType<T> where T : IBase {
            public static IEnumerable<T> list;
        }
    
        public static IEnumerable<T> Get<T>() where T : IBase 
            => PerType<T>.list; 
    
        public static void Set<T>(IEnumerable<T> newlist) where T : IBase 
            => PerType<T>.list = newlist;
    
        public static IEnumerable<T> GetByExample<T>(T ignoredExample) where T : IBase 
            => Get<T>(); 
    }
    

    Note that you should think carefully before adopting this approach about the distinction between compile-time type and run-time type. This method will happily let you store a runtime-typed IEnumerable<SomeType> variable both under SomeType and -if you cast it- under any of SomeType's base types, including IBase, with neither a runtime nor compiletype error - which might be a feature, or a bug waiting to happen, so you may want an if to check that.

    Additionally, this approach ignores threading; so if you want to access this data-structure from multiple threads, you probably want to add some locking. Reference read/writes are atomic, so you're not going to get corruption if you fail to lock, but stale data and race conditions are certainly possible.

    0 讨论(0)
  • 2020-12-24 12:54

    You don't constrain T in the private member; you constrain it at the class level.

    class Foo<T> where T : BaseClass
    {
        private IDictionary<T, IEnumerable<T>> _dataOfType;
    
        public Foo(IDictionary<T, IEnumerable<T>> dataOfType)
        {
            this._dataOfType = dataOfType;
        }   
    }   
    
    0 讨论(0)
提交回复
热议问题