object a = new Dog();
против
Dog a = new Dog();
В обоих случаях a.GetType()
дает Dog
.
Оба ссылаются на тот же конструктор (с той же иерархией).
Тогда, пожалуйста, скажите мне разницу между этими двумя утверждениями?
object a = new Dog();
против
Dog a = new Dog();
В обоих случаях a.GetType()
дает Dog
.
Оба ссылаются на тот же конструктор (с той же иерархией).
Тогда, пожалуйста, скажите мне разницу между этими двумя утверждениями?
Оба создают объект Dog. Только второй позволяет вам напрямую ссылаться на методы Собаки или иначе обращаться с ним как с собакой, например, если вам нужно передать объект методу в качестве параметра типа Dog
(или что-то в иерархии Собаки, которая является более конкретной чем просто object
).
object obj = new Dog();
// can only see members declared on object
var type = obj.GetType(); // can do this
Console.WriteLine(obj.ToString()); // also this
obj.Bark(); // Error! Bark is not a member of System.Object
Dog dog = new Dog();
// can do all of the methods declared for Object
dog.Bark(); // can finally use the method defined for Dog
new Dog()
- это выражение, которое создает новый экземпляр Dog
. Он вызывает безразмерный конструктор класса Dog
.
a
- это переменная: ячейка памяти в памяти, которая содержит ссылку на экземпляр Dog
после назначения.
Разница в том, что переменная Dog
может содержать только ссылку на экземпляр Dog
(или экземпляр любого класса, который происходит от Dog
), в то время как переменная object
может содержать ссылку на object
экземпляр (или экземпляр любого класса, который выводится из object
), который имеет класс Dog
).
Если у вас есть переменная Dog
, вы можете вызвать любой метод, определенный классом Dog
(и его базовые классы) в ссылочном экземпляре. Когда у вас есть переменная object
, вы можете вызывать только методы класса object
в экземпляре.
Ваша первая строка создает переменную типа object
.
Компилятор не позволит вам рассматривать это как Dog
.
Оба оператора содержат объявление и вызов конструктора. Вызовы конструктора идентичны, поэтому вы получаете Dog
в обоих случаях. Объявления различны: в первом случае вы объявляете переменную типа object
, суперкласс из Dog
; во втором случае вы объявляете переменную типа Dog
. Разница в том, что в следующем коде вы можете вызывать методы Dog
без приведения только тогда, когда вы объявляете переменную как Dog
; если вы объявите его как object
, вам понадобится бросок.
Оба оператора включают вызов конструктора по умолчанию Dog
, как вы сами указываете; поэтому очевидно, что в обоих случаях создается экземпляр Dog
. Это означает, что оба оператора завершают инициализацию переменной с идентичным экземпляром (это является частью инструкции после равных).
Однако в операторах также есть другая часть: объявление переменной (это часть выражения перед равными). В статически типизированных языках, таких как С#, каждая переменная - в общем, любое выражение - имеет статический тип:
object a = new Dog(); // static type: object / runtime type: Dog
Dog b = new Dog(); // static type: Dog / runtime type: Dog
Компилятор не позволит вам присваивать значение переменной, которую он не может доказать, имеет переменный статический тип, например. это не позволило бы
Cat c = new Dog(); // unless Dog derives from Cat, which we know isn't true
Поскольку все ссылочные типы неявно вытекают из System.Object
, присвоение переменной Dog
переменной статического типа object
ОК. Вы можете думать о "статическом типе" как о том, что объект "объявлен как". Вы всегда можете определить статический тип чего-либо, просто прочитав исходный код; это то, как это делает компилятор.
Затем также существует тип среды выполнения каждой переменной (выражение), о которой я говорил выше. В обоих случаях это одно и то же, потому что в обоих случаях мы создали Dog
. Вы можете думать о "типе времени выполнения" как о том, что на самом деле представляет собой объект. Тип времени выполнения не может быть определен только путем чтения источника; вы определяете его только во время работы программы, отсюда и название. В С# это делается путем вызова GetType
.
Должно быть очевидно, что тип времени выполнения - это то, что вы не можете сделать без 1; все должно "быть" в конце концов. Но зачем беспокоиться о том, чтобы изобрести понятие статического типа?
Вы можете думать о статических типах в качестве контракта между вами (программистом) и компилятором. Объявив статический тип b
Dog
, вы сообщаете компилятору, что вы не собираетесь использовать эту переменную для хранения чего-либо иного, кроме Dog
. Компилятор, в свою очередь, promises, чтобы вы не нарушили заявленную цель и не сделали ошибку, если попытаетесь это сделать. Это также предотвращает использование d
любым способом, который должен поддерживать не каждый тип Dog
.
Рассмотрим:
class Dog {
public void Woof();
}
Dog d = new Dog();
d.Woof(); // OK
object o = new Dog();
o.Woof(); // COMPILER ERROR
Последняя строка вызывает ошибку компилятора, так как она нарушает договор статической типизации: вы сказали компилятору, что o
может быть чем-либо, происходящим из System.Object
, но не все вещи, вытекающие из этого, имеют метод Woof
, Поэтому компилятор пытается защитить вас, сказав: "Что вы там делаете? Я не могу доказать, что все, что находится в o
, может woof! Что, если это был Cat
?".
Примечания:
¹ Это не означает, что каждый объект волшебным образом знает, что он "есть" на всех языках. В некоторых случаях (например, в С++) эта информация может использоваться при создании объекта, но затем "забывается", чтобы позволить компилятору больше свободы для оптимизации кода. Если это произойдет, объект все еще есть что-то, но вы не можете его высунуть и спросить его "кто вы?".
² На самом деле, в этом тривиальном примере это может доказать. Но он не захочет использовать эти знания, потому что соблюдение контракта статического типа - это целая точка.
Это полезно, если вы хотите использовать полиморфизм, и вы можете использовать абстрактный метод, который реализуется в Dog. Следовательно, таким образом объект является Собакой, даже так есть Object. Поэтому вы можете использовать этот способ, когда хотите использовать полиморфизм.