开发者

Why is it legal to store a derived type within a base class reference?

开发者 https://www.devze.com 2023-01-19 14:56 出处:网络
Suppose we have declared these two classes: public class Animal { //..... } public class Dog : Animal { //.....

Suppose we have declared these two classes:

public class Animal
{
    //.....
}

public class Dog : Animal
{
    //.....
}

Well, my question is: why below line of code is valid?

Animal animal = new Dog()开发者_C百科;

EDIT:

In e-book, "Professional C# 2008", there is a paragraph that says:

It's always safe to store a derived type within a base class reference.


The reason that it works is that a dog is an animal (because Dog inherits from Animal) so it is legal to assign a dog object to a variable of type Animal.

Assigning in the other direction is not legal. The following line will give an error because Animal does not inherit from dog:

// Error: Cannot implicitly convert type 'Animal' to 'Dog'.
// An explicit conversion exists (are you missing a cast?)
Dog dog = new Animal();

In some cases you may have a variable of type Animal but you know that actually it must be a Dog. In this case you can use a cast to make the assignment, but this cast can fail at run time if the object was not actually a Dog.

Animal animal = new Cat();

// Unable to cast object of type 'Cat' to type 'Dog'.
Dog dog = (Dog)animal;


You are creating a new Dog, but then you are treating (using) it as an Animal.

This is especially useful when you want to have some behaviour that is owned or exposed by the Animal, but the Dog may then override that with something different. The Dog can be used as either a Dog or as an Animal, because it is both.

Edit: here is a quick example, and i'm going to use an abstract base class so you can see the real value of this arrangement:

public abstract class Animal
{
    public abstract void Move();

    public virtual void MakeSignatureSound() 
    {
        Console.WriteLine("Ugggg");
    }
}

public class Dog : Animal 
{
    public override void Move() 
    {
        RunLikeAPuppy();
    }

    public override void MakeSignatureSound()
    {
        Console.WriteLine("Woof");
    }
}

public class CaveMan : Animal
{
    public override void Move() 
    {
        RunLikeANeanderthal();
    }
}

public class Cat : Animal
{
    public override void Move() 
    {
        RunLikeAKitteh();
    }

    public override void MakeSignatureSound()
    {
        Console.WriteLine("Meioww");
    }
}

Notice two things here:

  • all three derivatives from the Animal class had to override the Move() function, because in my base class i made the decision that all animals should have a Move(), but i didn't specify how they should move - that is up to the individual animal to specify
  • the CaveMan class didn't override the MakeSignatureSound() function, because it had already been defined in the base class and it was adequate for him

Now if i do this:

Animal caveman = new CaveMan();
Animal dog = new Dog();
caveman.MakeSignatureSound();
dog.MakeSignatureSound();

i will get this on the console:

Ugggg
Woof

But because i used an abstract base class, i cannot instantiate an instance of it directly, i can't do this:

Animal animal = new Animal();

As a developer, i want to ensure that when others (or myself) create a new Animal, it has to be a specific type, not just a pure Animal that had no behavior or characteristics.


Just to add an additional example of when this would be useful...

You could have a strongly typed list of animals...

List<Animal> animals = new List<Animal>();

and you would add instances of animal into that collection.

animals.Add(new Dog());
animals.Add(new Cat());

If you had animals like below.

class Animal
{
   public abstract string Run();
}

class Dog : Animal
{
   public override string Run()
   {
      return "Running into a wall.";
   }

}

class Cat : Animal
{
   public override string Run()
   {
     return "Running up a tree";
   }

}

you could then safely loop through the collection.

foreach(var animal in animals)
   Console.WriteLine(animal.Run());

This introduces other concepts like abstract methods and overriding that you should look into also..

Hope this is of use.

Good luck!


Because Dog inherits from Animal - so Dog can be treated as though it were an Animal (it is an Animal in fact, it supports the same 'interface' - that is, all properties / methods on Animal are guaranteed implemented by Dog).


It is because of the relationship that you have declared here

public class Dog : Animal

What this line is basically saying is that every Dog IS A Animal

Now, if every Dog is an animal, then when you do this

 Animal animal = new Dog()

what it means is that i am taking a Dog but referring to it as an animal.

An analogous example would be to refer to you as a Mammal - you are human but are also a mammal so if i called you a mammal that would still be correct.

It does Not necessarily qualify you in detail but still correct per se.


In C# (and some other languages) every object has two types: static type and dynamic type. Static type of an object (in your example: Animal) is determined at compile time and dynamic type (in your example: Dog) is determined at run time. Consider the following:

Animal animal;
if (userInput) 
    animal = new Wolf();
else 
    animal = new Dog();

The compiler has no way to determine which dynamic type animal will have. It is only determined at run time
Dynamic type should always be at least the static type of an object. These are not allowed and will result in compilation errors:

Dog d = new Animal(); // X
Animal a = new Car(); // X (assuming car does not inherit from animal)

Why is it useful? Unlike in dynamically typed languages (where there is only dynamic type and no static), the compiler can check typing errors. This is good because we want to catch errors as early as possible.
In my example, the compiler does not know if animal is Wolf or Dog, but both derive from Animal so it can be sure any Aminal operation can be performed on animal. Trying to perform other operations will result in compilation error.
On the other hand, we can achieve powerful stuff that is not possible without the "dual type" system. Lets assume Aminal has an operation eat. Both Dog an Wolf also implement eat, each in it's own way. Let's look at the following example:

Animal a = new Animal();
Animal d = new Dog();

a.eat(); // Animal's eat
d.eat(); // Dog's eat

Here we can see that although the static type of d is Animal, the actual version of the function that is called is determined by dynamic type. This allows us to do a thing called polymorphism. I will show you an example:

Animal zoo[100]; // each animal in the zoo array is a static type Animal
zoo[0] = new Dog(); // first element of the array is of dynamic type Dog
zoo[1] = new Cat();
zoo[2] = new Rabbit();
...
// Now the array holds different types of animals. We want to feed them all, but each one in it's own way.
foreach(Animal a in zoo)
    a.eat();

To summarize:

  • Static type known at compile time. Used to determine if an operation is legal to perform on an object.
  • Dynamic type known only at run time. Used to choose which operation to perform. There is another term for this: dynamic dispatch


Just assume this line is not legal Animal animal = new Dog();

public class Animal
{
    //.....
}


public class Dog : Animal
{
    //.....
}

Now in my main program I have a function called Feed.

void Feed(Dog d)
{

  //....
}

And then I have added another class Cat

public class Cat : Animal
{
    //.....
}

then in my main program I will have to write another function for Feed specially for cat class

void Feed(Dog d)
{

  //....
}

void Feed(Cat d)
{

  //....
}

In order to avoid coding duplicate functions (in case of derived classes), it is legal and safe to store a derived type within a base class reference.

Now instead of above two functions I can write a single Feed function and takes a parameter of a base class.

Animal animal = new Dog();

Animal animal2 = new Cat();

Feed(animal);
Feed(animal2);

void Feed(Animal A)
{

  //....
}
0

精彩评论

暂无评论...
验证码 换一张
取 消