Почему допустимо хранить производный тип в ссылке на базовый класс? - PullRequest
3 голосов
/ 10 октября 2010

Предположим, мы объявили эти два класса:

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

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

Ну, мой вопрос: почему строка кода ниже действительна?

Animal animal = new Dog();

EDIT:

В электронной книге "Professional C # 2008" есть параграф, который гласит:

Всегда безопасно хранить производный тип в пределах ссылки на базовый класс.

Ответы [ 7 ]

9 голосов
/ 10 октября 2010

Причина, по которой это работает, заключается в том, что собака является животным (поскольку Dog наследует от Animal), поэтому допустимо назначать объект собаки переменной типа 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, но вы знаете, что на самом деле это должна быть собака. В этом случае вы можете использовать приведение, чтобы выполнить назначение, но это приведение может завершиться неудачей во время выполнения, если объект на самом деле не был Собакой.

Animal animal = new Cat();

// Unable to cast object of type 'Cat' to type 'Dog'.
Dog dog = (Dog)animal;
5 голосов
/ 10 октября 2010

Вы создаете новый Dog, но затем вы рассматриваете (используете) его как Animal.

Это особенно полезно, когда вы хотите иметь какое-то поведение, которое принадлежит Animal, но Dog может затем переопределить это с чем-то другим. Dog может использоваться как Dog или Animal, потому что это оба.

Редактировать: вот краткий пример, и я собираюсь использовать абстрактный базовый класс, чтобы вы могли увидеть реальную ценность этого устройства:

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(), потому что в моем базовом классе я принял решение, что у всех животных должен быть Move(), но я не сделал этого указать, как они должны двигаться - это зависит от конкретного животного, чтобы указать
  • класс CaveMan не перекрывал функцию MakeSignatureSound(), потому что она уже была определена в базовом классе и была адекватной для него

Теперь, если я сделаю это:

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

я получу это на консоли:

Ugggg
Woof

Но поскольку я использовал абстрактный базовый класс, я не могу создать его экземпляр напрямую, я не могу сделать это:

Animal animal = new Animal();

Как разработчик, я хочу убедиться, что когда другие (или я) создают новый Animal, это должен быть определенный тип, а не просто чистый Animal, который не имеет поведения или характеристик.

2 голосов
/ 10 октября 2010

Просто чтобы добавить дополнительный пример того, когда это будет полезно ...

Вы можете иметь строго типизированный список животных ...

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

Это вводит другие понятия, такие как абстрактные методы и переопределения, которые вы должны изучить также

Надеюсь, это полезно.

Удачи!

1 голос
/ 10 октября 2010

В 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)

Почему это полезно?В отличие от языков с динамической типизацией (где есть только динамический тип и нет статического), компилятор может проверять ошибки ввода.Это хорошо, потому что мы хотим отлавливать ошибки как можно раньше.
В моем примере компилятор не знает, является ли animal Wolf или Dog, но оба являются производными от Animal, так что это может бытьуверен, что любая операция Aminal может быть выполнена на animal.Попытка выполнить другие операции приведет к ошибке компиляции.
С другой стороны, мы можем добиться мощных возможностей, которые невозможны без системы «двойного типа».Предположим, что Aminal имеет операцию eat.Оба Dog и Wolf также реализуют питание, каждое по-своему.Давайте рассмотрим следующий пример:

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

Подведем итог:

  • Статический тип, известный во время компиляции.Используется для определения допустимости операции над объектом.
  • Динамический тип известен только во время выполнения.Используется для выбора операции для выполнения.Для этого есть другой термин: динамическая отправка
1 голос
/ 10 октября 2010

Это из-за отношений, которые вы объявили здесь

public class Dog : Animal

В сущности, эта строка говорит о том, что каждая собака - животное

Теперь, если каждая собака - животное, то когда вы делаете это

 Animal animal = new Dog()

это значит, что я беру Собаку, но называю ее животным.

Аналогичным примером может быть обращение к вам как к млекопитающему - вы человек, но также млекопитающее, поэтому, если бы я назвал вас млекопитающим, это все равно было бы правильно.

Это не обязательно квалифицирует вас подробно, но все же правильно само по себе.

1 голос
/ 10 октября 2010

Поскольку Dog наследуется от Animal, то есть Dog может рассматриваться как Animal (на самом деле равно Animal, он поддерживает тот же интерфейс) '- то есть все свойства / методы в Animal гарантированно реализованы Dog).

0 голосов
/ 10 октября 2010

Просто предположим, что эта строка недопустима 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)
{

  //....
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...