views:

390

answers:

6

Hi all,

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?

+9  A: 

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;
    }   
}   
Mark Rushakoff
That is right, and probably the reason it does not compile. However this is not what I intend, I would like to have a type constrained member, but not a type constraint on the class.
Marcel
+3  A: 

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;
Enrico Campidoglio
+1, but shouldn't that be Dictionary<T, IEnumerable<T>> ?
Lucas
@Lucas I don't see what you mean
Enrico Campidoglio
@Marcel, Responding here, but this applies to my answer below as well... This will NOT give you multiple dictionaries. it will, as you specify, give you ONE dictionary, whose key collection is a collection of Ts, and whose Values collection is a collection of any objects that implememt IEnumerable of T, i.e. (that could be an array of Ts, a List of Ts, a Linked List of Ts, etc. Or any combination of these items... ) This may still not be what you want, but it is what you said...
Charles Bretana
@Charles Bretana: you are right about just needing ONE dictionary. I deleted my other comment.
Marcel
+3  A: 
  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

Rubys
Well-crafted answer. However, together with other good answers, this turns out to be more complicated that I thought. Since currently I only have 2 derived classes, I now tend to manage two separate enumerables directly as properties in my class. :-(
Marcel
I did some testing on the implementation 280Z28 provided, it works and it works great, on all classes. This is a neat, weird idea.
Rubys
A: 

Try this:

public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>>  { } 
Charles Bretana
Not what I really need, see also my comment to johnny g.
Marcel
@Marcel, Commented above under Enrico's answer
Charles Bretana
+2  A: 

You've titled the question suggesting that there's some constraint that key and value share; but you're example suggests otherwise; that actually your value constraint is a type which is the key.

You may not even need a dictionary to be able to due 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 Enumerable<T> list;
    }
    public static IEnumerable<T> Get<T>() where T : IBase {
     return 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 {
        return 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 is possibly not what you want, so you may want an if to check that.

Eamon Nerbonne
+1 for the first remark. You are right about the constraint of the key.
Marcel
Thanks for the code. This suits my case very well. I use this now.
Marcel
+1  A: 

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);
this. __curious_geek
That's nice. However, I now have already implemented the solution provided by Eamon Nerbonne
Marcel