为什么在基类引用中存储派生类型是合法的?

时间:2010-10-10 07:23:37

标签: c# .net inheritance polymorphism

假设我们已经声明了这两个类:

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

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

好吧,我的问题是:为什么下面的代码行有效?

Animal animal = new Dog();

修改

在电子书“专业C#2008”中,有一段说:

  

将派生类型存储在基类引用中始终是安全的。

7 个答案:

答案 0 :(得分:9)

它起作用的原因是狗is an动物(因为来自Animal的Dog inherits)所以将狗对象分配给Animal类型的变量是合法的。

在另一个方向分配是不合法的。以下行将给出错误,因为Animal不从dog继承:

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

在某些情况下,您可能有一个类型为Animal的变量,但您知道它实际上必须是Dog。在这种情况下,您可以使用强制转换进行赋值,但如果对象实际上不是Dog,则此强制转换可能会在运行时失败。

Animal animal = new Cat();

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

答案 1 :(得分:5)

您正在创建新的Dog,但之后您正在将其视为Animal

当您希望Animal拥有或公开某些行为时,这尤其有用,但Dog可能会覆盖不同的行为。 Dog可以用作DogAnimal,因为它们都是。

编辑:这是一个快速示例,我将使用抽象基类,以便您可以看到此安排的真正价值:

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");
    }
}

请注意两件事:

  • 来自Animal的所有三个导数都来覆盖Move()函数,因为在我的基类中我决定所有动物都应该有{{1}但是我没有指定他们应该如何移动 - 这取决于指定的个体动物
  • Move()类没有覆盖CaveMan函数,因为它已经在基类中定义并且对他来说足够了

现在,如果我这样做:

MakeSignatureSound()

我会在控制台上看到这个:

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

但是因为我使用了抽象基类,我不能直接实例化它的实例,我不能这样做:

Ugggg
Woof

作为开发人员,我想确保当其他人(或我自己)创建新的Animal animal = new Animal(); 时,它必须是特定类型,而不仅仅是没有行为或特征的纯Animal

答案 2 :(得分:2)

只是添加另一个有用的例子......

你可以有一个强类型的动物名单......

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

您可以将动物实例添加到该集合中。

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

如果你有下面的动物。

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";
   }

}

然后你可以安全地循环收集。

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

这引入了其他概念,如抽象方法和覆盖,你也应该考虑..

希望这是有用的。

祝你好运!

答案 3 :(得分:1)

由于Dog继承自Animal,所以Dog可以被视为Animal 一个{{1}实际上,它支持相同的“接口” - 也就是说,Animal上的所有属性/方法都由Animal}保证实现。

答案 4 :(得分:1)

这是因为你在这里声明的关系

public class Dog : Animal

这条线基本上说的是每只狗都是动物

现在,如果每只狗都是动物,那么当你这样做时

 Animal animal = new Dog()

这意味着我正在带狗,但将它称为动物。

类似的例子是将你称为哺乳动物 - 你是人类,但也是哺乳动物,所以如果我称你为一种仍然正确的哺乳动物。

它不一定能够详细说明你的资格,但本身仍然是正确的。

答案 5 :(得分:1)

在C#(和其他一些语言)中,每个对象都有两种类型:静态类型动态类型。对象的静态类型(在您的示例中为Animal)在编译时确定,动态类型(在您的示例中为Dog)在运行时确定。请考虑以下事项:

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

编译器无法确定动物将拥有哪种动态类型。它仅在运行时确定 动态类型应始终至少是对象的静态类型。这些是不允许的,并且会导致编译错误:

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

为什么有用?与动态类型语言(只有动态类型且没有静态)不同,编译器可以检查输入错误。这很好,因为我们希望尽早发现错误 在我的示例中,编译器不知道animalWolf还是Dog,但两者都来自Animal,因此可以确定任何Aminal操作都可以在animal上执行。尝试执行其他操作将导致编译错误 另一方面,如果没有“双重型”系统,我们可以实现强大的功能。假设Aminal有一个操作eatDogWolf都可以通过自己的方式实现吃饭。我们来看下面的例子:

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

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

在这里我们可以看到虽然d的静态类型是Animal,但是被调用的函数的实际版本由动态类型决定。这允许我们做一个叫做多态的的事情。我会告诉你一个例子:

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();

总结:

  • 编译时已知的静态类型。用于确定某个操作对于对象执行是否合法。
  • 仅在运行时知道的动态类型。用于选择要执行的操作。还有另一个术语:动态调度

答案 6 :(得分:0)

假设此行不合法Animal animal = new Dog();

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


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

现在在我的主程序中,我有一个名为Feed的功能。

void Feed(Dog d)
{

  //....
}

然后我添加了另一个类Cat

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

然后在我的主程序中,我将不得不为Feed专门为cat类编写另一个函数

void Feed(Dog d)
{

  //....
}

void Feed(Cat d)
{

  //....
}

为了避免编写重复的函数(在派生类的情况下),将派生类型存储在基类引用中是合法且安全的。

现在我可以编写单个Feed函数而不是上面两个函数,并获取基类的参数。

Animal animal = new Dog();

Animal animal2 = new Cat();

Feed(animal);
Feed(animal2);

void Feed(Animal A)
{

  //....
}