Reputation: 4755
In my example, I have many classes (Orange
, Pear
, Apple
, ...) that extend a base class Fruit
.
I am creating a model class that contains dictionaries of each type of Fruit
mapped to their integer ids. I'd like to avoid making many field variables like this:
Dictionary<int, Orange> _oranges = new Dictionary<int, Orange>();
I thought I might be able to create a "generic" dictionary, in which I map other dictionaries to Fruit
types:
Dictionary<Type, Dictionary<int, Fruit>> _fruits = new Dictionary<Type, Dictionary<int, Fruit>>();
To insert into this structure, I use a method like so:
public void Insert(Fruit fruit)
{
_fruits[fruit.GetType()][fruit.Id] = fruit;
}
The problem comes when I try to retrieve the stored values, as in this method:
public IEnumerable<T> GetFruits<T>() where T : Fruit
{
return (IEnumerable<T>) _fruits[typeof(T)].Values.ToArray();
}
which would be called like GetFruits<Orange>()
. The cast fails with this error:
System.InvalidCastException: 'Unable to cast object of type 'Example.Fruit[]' to type 'System.Collections.Generic.IEnumerable`1[Example.Orange]'.'
How can I do what I'm trying to do?
Upvotes: 1
Views: 2998
Reputation: 107237
The reason why you get the error is because the type of Values
in the innermost Dictionary of _fruits
is inherently a map of the base class Fruit
:
Dictionary<int, Fruit>
Specifically, the Values property is defined as ICollection<T>
.
At run time, you aren't allowed you to directly recast Values
, which is a ICollection<Fruit>
to IEnumerable<T>
- e.g. IEnumerable<Orange>
.
To resolve this, you will effectively need to traverse the Values
collection and downcast (and possibly also filter) by type.
(Even though you 'know' that your code has only allowed Oranges
into the fruits[typeof(Orange)]
dictionary, from the type system's perspective, the type is still ICollection<Fruit>
)
As per the other answers, you can use any number of ways to do this casting and filtering:
foreach(T item in Values)
.Cast<T>
- This will however throw if somehow a different Fruit is found.OfType<T>
- This will exclude items of the incorrect type.There's more detail on these approaches discussed here
Upvotes: 1
Reputation: 26989
You can use OfType
method:
var _fruits = new Dictionary<Type, Dictionary<int, Fruit>>();
public IEnumerable<T> GetFruits<T>() where T : Fruit
{
return _fruits.OfType<T>().ToArray();
}
Upvotes: 1
Reputation: 270820
I think you just have to cast it using Cast<T>
.
return _fruits[typeof(T)].Values.Cast<T>();
Using a (IEnumerable<T>)
to cast does not work because it will cast the whole thing. You may know this already: List<object>
can't be casted to List<int>
. The same phenomenon happens here.
Therefore, we should use Cast<T>
. This method will cast each element of the enumerable to the specified type, and returning the resulting enumerable.
Upvotes: 2