Let's say I have:
public class Animal
{
virtual public void Attack() {};
}
public class Lion : Animal
{
public override void Attack() { base.Attack(); }
}
public class Boar : Animal
{
public override void Attack() { base.Attack(); }
}
And containers of these two types of animals:
Dictionary<int, Lion> lions;
Dictionary<int, Boar> boars;
Is there a way to cast 'lions' and 'boars' so that I can pass them into a function like this?
void IterateTable(Dictionary<int, Animal> dictionary)
{
foreach(var entry in dictionary)
entry.Value.Attack();
}
Maybe something like this?
void IterateTable<T>(Dictionary<int, T> dictionary)
where T : Animal
{
foreach(var entry in dictionary)
entry.Value.Attack();
}
Your code works as written. When the Animal in the dictionary's value has its Attack() method called, it invokes the appropriate, animal-specific method. This is called covariance. You can provide a more specific type to the dictionary than required by its signature.
You can modify your code as follows to see:
void Main()
{
Dictionary<int, Animal> dictionary = new Dictionary<int, Animal>()
{
[1] = new Lion(),
[2] = new Boar()
};
IterateTable(dictionary);
}
public class Animal
{
virtual public void Attack() { Console.WriteLine("Default animal attack"); }
}
public class Lion : Animal
{
public override void Attack() { Console.WriteLine("Lion attack"); }
}
public class Boar : Animal
{
public override void Attack() { Console.WriteLine("Boar attack"); }
}
void IterateTable(Dictionary<int, Animal> dictionary)
{
foreach (var entry in dictionary)
entry.Value.Attack();
}
Output:
Lion attack
Boar attack
A Dictionary<TKey,TValue>
is an IEnumerable<KeyValuePair<TKey,TValue>>
.
So, the easy way is to use `ToDictionary(), just upcast the value in your value selector:
Dictionary<string,Lion> dict = new Dictionary<string,Lion>( GetMeSomeLions() );
Dictionary<string,Animal> dictAnimals = dict.ToDictionary(
kvp => kvp.Key, // Key selector
kvp => (Animal) kvp.Value // Value selector
);
The problem is that if you pass in a dictionary, there is nothing preventing IterateTable
from adding an element to it. That is a problem. Since IterateTable
thinks it's a Dictionary<Animal>
, it could add either a Boar or Lion, or both. This would obviously not work.
void IterateTable(Dictionary<int,Animal> dictionary)
{
dictionary.Add(1, new Boar()); //This would fail if you'd passed in a dictionary of Lions
dictionary.Add(2, new Lion()); //This would fail if you'd passed in a dictionary of Boars
}
Since IterateTable
doesn't actually have to add any elements to the dictionary, you can change its signature so that it accepts a read-only object such as IEnumerable<T>
. Because that interface disallows adding anything, it's legal, and the compilation error goes away.
void IterateTable(IEnumerable<Animal> animals)
{
foreach (var entry in animals)
entry.Attack();
}
Now you can write this, and it'll compile fine:
var lions = new Dictionary<int, Lion>();
var boars = new Dictionary<int, Boar>();
IterateTable(lions.Values);
IterateTable(boars.Values);
By the way, this concept is called covariance and is a really really important part of understanding generics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With