Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you cast a dictionary<int, child> to dictionary<int, parent>? [duplicate]

Tags:

c#

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();
}
like image 993
greenbit Avatar asked Feb 04 '20 23:02

greenbit


4 Answers

Maybe something like this?

void IterateTable<T>(Dictionary<int, T> dictionary)
    where T : Animal
{
    foreach(var entry in dictionary)
        entry.Value.Attack();
}
like image 71
Dennis_E Avatar answered Oct 23 '22 21:10

Dennis_E


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

like image 43
Eric J. Avatar answered Oct 23 '22 22:10

Eric J.


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
);
like image 1
Nicholas Carey Avatar answered Oct 23 '22 22:10

Nicholas Carey


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.

like image 1
John Wu Avatar answered Oct 23 '22 22:10

John Wu