Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When a Child class is added to a List<parent>, is the child-specific data lost?

Tags:

c#

inheritance

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:

  • Extracting out the dogs using the isDog bool and then calling the Dog constructor to make a new one outside of the function that returns a list of animals
  • Giving the dog class a constructor that takes in an animal, but still no way to preserve tail length

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

like image 998
bzimm01 Avatar asked Jun 07 '16 20:06

bzimm01


2 Answers

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:

  1. There are two syntax errors where you missed a semicolon.

  2. Fields cannot be interface members, so you need to make them properties.

  3. 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;
    }
    
  4. In getAnimals, you need to initialize the list:

    List<Animal> animals = new List<Animal>();
    
  5. 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.

  6. totalTailLength expects an enumerable of IDogs but sample.Where() returns IAnimals (or Animals 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>());
like image 143
poke Avatar answered Sep 30 '22 06:09

poke


No, of course the data specific to child class are not lost.

But please :

  • define your instance variable as private and provide a public property
  • remove the variable isDog !This is an error in your conception. Tomorrow, if you create class Cat, Mouse, Duck, will you add instance variables isCat, isMouse, isDuck ?

If you need to extract only Dog animals, you can simply use Linq :

IEnumerable<IDog> dogs = animals.OfType<IDog>();
like image 36
gentiane Avatar answered Sep 30 '22 05:09

gentiane