I have a function that makes a list of animals, some of which are dogs, and some of which are just animals. That function returns that list to a different function that wants to extract out just the dogs, which is possible because the Animal class has a bool isDog.
My question is, is it possible to extract the dog-specific(child-specific) data from the animal(parent) class, aka. can an animal still be a dog under the hood?
Things I have tried:
I have these two interfaces
public interface IAnimal
{
string eyeColor;
int numLegs;
}
public interface IDog: IAnimal
{
double tailLength;
}
and these two implementations
public class Animal: IAnimal
{
public string eyeColor{get;set}
public int numLegs {get; set;}
public bool isDog;
public Animal(string eyes, int legs){
this.eyeColor = eyes;
this.numLegs = legs
this.isDog = false;
}
}
public class Dog: Animal, IDog
{
public double tailLength {get;set;}
public Dog(double tail, string eyes)
{
this.tailLength = tail;
this.eyeColor = eyes;
this.numLegs = 4;
this.isDog = true;
}
}
and this is the current state of the two functions
List<Animal> getAnimals()
{
List<Animal> animals;
Animal a1 = new Animal("Blue", 4);
animals.Add(a1);
Animal a2 = new Animal("Green", 2);
animals.Add(a2);
Dog d1 = new Dog(2.3, "Brown");
animals.Add(d1);
Dog d2 = new Dog(3.5, "Black");
animals.Add(d2);
return animals;
}
void foo(){
IEnumerable<IAnimal> sample = getAnimals();
var x = totalTailLength(sample.Where(d => d.isDog));
}
double totalTailLength(IEnumerable<IDog> dogs){
return dogs.Sum(d => d.tailLength);
}
Thanks
Yes, you can absolutely do that. And if you use your exact code and fix a few errors, then you could see that it works:
There are two syntax errors where you missed a semicolon.
Fields cannot be interface members, so you need to make them properties.
Dog
needs to call the parent’s constructor, which also removes the need to set those properties manually:
public Dog(double tail, string eyes) : base(eyes, 4)
{
this.tailLength = tail;
this.isDog = true;
}
In getAnimals
, you need to initialize the list:
List<Animal> animals = new List<Animal>();
In foo
, sample
is of type IEnumerable<IAnimal>
, so the elements are of type IAnimal
. That type however does not have the isDog
property (since it’s on the class but not on the interface). So either change the type to IEnumerable<Animal>
, or move the member to the interface.
totalTailLength
expects an enumerable of IDog
s but sample.Where()
returns IAnimal
s (or Animal
s as per above). Since you filtered the elements, you should cast the enumerable:
var x = totalTailLength(sample.Where(d => d.isDog).Cast<IDog>());
And then everything will compile and—if you actually print out the results—you get the correct results.
The type of an object is not attached to the variable that references that object but to the object itself. So every Dog
inside your animal list still knows that it’s a dog. And that actually allows you to get rid of the isDog
member since you can just check whether an object is of a specific type using the is
operator:
IAnimal someAnimal = new Dog();
bool isADog = someAnimal is Dog; // true
And you can also use that with LINQ to filter for Dog
elements of your IAnimal
list using Enumerable.OfType<T>
:
var x = totalTailLength(sample.OfType<IDog>());
No, of course the data specific to child class are not lost.
But please :
If you need to extract only Dog animals, you can simply use Linq :
IEnumerable<IDog> dogs = animals.OfType<IDog>();
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