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;
}
}
- 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.
- 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 aboutIEnumerable<Base>
being as good asIEnumerable<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>> { }
精彩评论