1

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?

Mitch Talmadge
  • 4,638
  • 3
  • 26
  • 44

3 Answers3

2

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Additionally / alternatively, you can also use [`OfType`](https://stackoverflow.com/a/41951319/314291) to filter the type and avoid possible InvalidCast exceptions – StuartLC Nov 18 '17 at 08:29
  • Thank you, this did exactly what I wanted and I understand why my code wasn't working before :) – Mitch Talmadge Nov 18 '17 at 08:36
1

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();
}
CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
1

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:

  • A filtered foreach, 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

StuartLC
  • 104,537
  • 17
  • 209
  • 285