You can actually do this with basic inheiritance and dropping in virtual methods as desired.
I made this as a simple console example for reference.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InheiritanceAndBaseDerivation
{
class Program
{
static void Main(string[] args)
{
AnimalLover me = new AnimalLover();
me.Draw();
Console.ReadLine();
}
}
public class AnimalLover
{
List<Animal> animals = new List<Animal>();
public AnimalLover()
{
animals.Add(new Animal());
animals.Add(new Cat());
animals.Add(new Dog());
animals.Add(new Lizard());
}
public void Update()
{
}
public void Draw()
{
foreach (Animal item in animals)
{
item.Draw();
}
}
}
public class Animal
{
public string objectTypesName = "Animal";
public string noise = "...";
public string growl = "grrrr";
public virtual string Speak
{
get { return noise; }
set { noise = value; }
}
public virtual void Update()
{
}
public virtual void Draw()
{
Console.WriteLine(objectTypesName + " " + Speak);
}
}
public class Cat : Animal
{
public Cat()
{
objectTypesName = "Cat";
Speak = "Meow";
}
}
public class Dog : Animal
{
public Dog()
{
objectTypesName = "Dog";
Speak = "Ruff";
}
// because dogs are very vocal
public override string Speak
{
get
{
return growl + " " + noise;
}
set
{
noise = value;
}
}
}
public class Lizard : Animal
{
public Lizard()
{
objectTypesName = "Lizard";
}
}
}
// Output as follows // // Animal ... // Cat Meow // Dog grrrr Ruff // Lizard ... //
Note that the Cat the Dog and the Lizard All are all collected and called by the AnimalLover. When we call Draw on the AnimalLover Instance he asks them all to speak.
Each speaks differently in its own way even though the AnimalLovers List is only a list of animals.
Cats call to their base.Draw and then that calls to the Cats derived Speak.
Conversly.
Dogs will call to there own Overridden Speak from there animal inheirited Draw.
Where lizards use the base of calls (animal), with the exception that only the derived objectTypeName has been set and it is shown in the output.
You don’t want to cast were its avoidable (IS AS (type)T) because you don’t want the overhead or garbage associated with boxing and unboxing to occur. As you can see by the output and code, calling directly on the Generic Collections items allows you to avoid that.
The typical cost to the above is that at least most of the required data references across the classes or arbitrarily named data variables be declared in the base class. but not specifically that they be instantiated or allocated data which is very much design specific.
However as shown in the output the objectTypeName has been set literally by name in the constructor of each Type at instantiation and you could if absolutely neccessary, cast a Animal from the AnimalLovers list into that specific Type directly with a switch case statement based on the objectTypeName variable.
var a = animals[0];
string s = animals[0].objectTypesName;
switch(s)
{
case(“Lizard”):
var n = (lizard)(a);
n.TailWhip();
Break;
}
Note with TailWhip() we do something only a lizard can do, that is not callable for some specific reason from update or draw, or some other basic, animal decendant purposed base bridging method. WARNING! this basically could cause boxing unboxing of animal and could generate garbage so you want to avoid having to call TailWhip() by overriding Lizards update and calling it from there when required. A second base Update is also possible say one that takes a string as a command from the animal lover to the animals ![]()