Семантика присваивания
Рассмотрим присваивание:
x = e.
Чтобы присваивание было допустимым, типы переменной x и выражения e должны быть согласованными. Пусть сущность x согласно объявлению принадлежит классу T. Будем говорить, что тип T основан на классе T и является базовым типом x, так что базовый тип определяется классом объявления. Пусть теперь в рассматриваемом нами присваивании выражение e связано с объектом типа T1.
Определение: тип T1 согласован по присваиванию с базовым типом T переменной x, если класс T1 является потомком класса T.
Присваивание допустимо, если и только если имеет место согласование типов. Так как все классы в языке C# - встроенные и определенные пользователем - по определению являются потомками класса Object, то отсюда и следует наш частный случай - переменным класса Object можно присваивать выражения любого типа.
Несмотря на то, что обстоятельный разговор о наследовании, родителях и потомках нам еще предстоит, лучше с самого начала понимать отношения между родительским классом и классом-потомком, отношения между объектами этих классов. Класс-потомок при создании наследует все свойства и методы родителя. Родительский класс не имеет возможности наследовать свойства и методы, создаваемые его потомками. Наследование - это односторонняя операция от родителя к потомку. Ситуация с присваиванием симметричная. Объекту родительского класса присваивается объект класса-потомка. Объекту класса-потомка не может быть присвоен объект родительского класса. Присваивание - это односторонняя операция от потомка к родителю. Одностороннее присваивание реально означает, что ссылочная переменная родительского класса может быть связана с любыми объектами, имеющими тип потомков родительского класса.
Например, пусть задан некоторый класс Parent, а класс Child - его потомок, объявленный следующим образом:
class Child:Parent {...}
Пусть теперь в некотором классе, являющемся клиентом классов Parent и Child, объявлены переменные этих классов и созданы связанные с ними объекты:
Parent p1 = new Parent(), p2 = new Parent(); Child ch1 = new Child(), ch2 = new Child();
Тогда допустимы присваивания:
p1 = p2; p2= p1; ch1=ch2; ch2 = ch1; p1 = ch1; p1 = ch2;
Но недопустимы присваивания:
ch1 = p1; ch2 = p1; ch2 = p2; ch1 = p2;
Заметьте, ситуация не столь удручающая - сын может вернуть себе переданный родителю объект, задав явное преобразование. Так что следующие присваивания допустимы:
p1 = ch1; ... ch1 = (Child)p1;
Семантика присваивания справедлива и для другого важного случая - при рассмотрении соответствия между формальными и фактическими аргументами процедур и функций. Если формальный аргумент согласно объявлению имеет тип T, а выражение, задающее фактический аргумент, имеет тип T1, то имеет место согласование типов формального и фактического аргумента, если и только если класс T1 является потомком класса T. Отсюда незамедлительно следует, что если формальный параметр процедуры принадлежит классу Object, то фактический аргумент может быть выражением любого типа.