开发者

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

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)

private IDictionary<Type, IEnumerable<T>> _dataOfType where T: BaseClass; //does not compile!

The concrete classes I want to store, all derive from BaseClass, therefore the idea to use it as constraint. The compiler complains that 开发者_开发知识库it expects a semicolon after the member name.

If it would work, I would expect this would make the later retrieval from the dictionary simple like:

IEnumerable<ConcreteData> concreteData;
_sitesOfType.TryGetValue(typeof(ConcreteType), out concreteData);

How to define such a dictionary?


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);


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.


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;
    }   
}   


  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


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;


Try this:

public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>>  { } 
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜