Страница 260 из 371
Console.WriteLine("Testing.FromString Test");
tm.FromStringTest ();
Console.WriteLine("Testing.CheckUncheck Test");
tm.CheckUncheckTest ();
}
}
}
Класс Class1 содержит точку входа Main и ничего более. В процедуре Main создается объект tm класса Testing, затем поочередно вызываются семь методов этого класса. Каждому вызову предшествует выдача соответствующего сообщения на консоль. Каждый метод — это отдельный пример, подлежащий обсуждению.
Семантика присваивания
Рассмотрим присваивание:
х = е.
Чтобы присваивание было допустимым, типы переменной х и выражения е должны быть согласованными. Пусть сущность х согласно объявлению принадлежит классу T. Будем говорить, что тип T основан на классе T и является базовым типом х, так что базовый тип определяется классом объявления. Пусть теперь в рассматриваемом нами присваивании выражение е связано с объектом типа Т1.
Определение: тип T1 согласован по присваиванию с базовым типом T переменной х, если класс T1 является потомком класса T.
Присваивание допустимо, если и только если имеет место согласование типов. Так как все классы в языке C# — встроенные и определенные пользователем — по определению являются потомками класса Object, то отсюда и следует наш частный случай — переменным класса Object можно присваивать выражения любого типа.
Несмотря на то, что обстоятельный разговор о наследовании, родителях и потомках нам еще предстоит, лучше с самого начала понимать отношения между родительским классом и классом-потом ком, отношения между объектами этих классов. Класс-потомок при создании наследует все свойства и методы родителя. Родительский класс не имеет возможности наследовать свойства и методы, создаваемые его потомками. Наследование — это односторонняя операция от родителя к потомку. Ситуация с присваиванием симметричная. Объекту родительского класса присваивается объект класса-потомка. Объекту класса-потомка не может быть присвоен объект родительского класса. Присваивание — это односторонняя операция от потомка к родителю. Одностороннее присваивание реально означает, что ссылочная переменная родительского класса может быть связана с любыми объектами, имеющими тип потомков родительского класса.
Например, пусть задан некоторый класс Parent, а класс Child — его потомок, объявленный следующим образом:
class Child: Parent {…}
Пусть теперь в некотором классе, являющемся клиентом классов Parent и Child, объявлены переменные этих классов и созданы связанные с ними объекты:
Parent p1 = new Parent(), р2 = new Parent();
Child ch1 = new Child(), ch2 = new Child();
Тогда допустимы присваивания:
p1 = р2; р2= p1; ch1=ch2; ch2 = ch1; p1 = ch1; p1 = ch2;
Но недопустимы присваивания:
ch1 = p1; ch2 = p1; ch2 = p2; ch1 = p2;
Заметьте, ситуация не столь удручающая — сын может вернуть себе переданный родителю объект, задав явное преобразование. Так что следующие присваивания допустимы:
p1 = ch1;… ch1 = (Child)p1;
Семантика присваивания справедлива и для другого важного случая — при рассмотрении соответствия между формальными и фактическими аргументами процедур и функций. Если формальный аргумент согласно объявлению имеет тип T, а выражение, задающее фактический аргумент, имеет тип T1 то имеет место согласование типов формального и фактического аргумента, если и только если класс Т1 является потомком класса T. Отсюда незамедлительно следует, что если формальный параметр процедуры принадлежит классу Object, то фактический аргумент может быть выражением любого типа.
Преобразование к типу object
Рассмотрим частный случай присваивания х = е; когда х имеет тип object, в этом случае гарантируется полная согласованность по присваиванию — выражение е может иметь любой тип. В результате присваивания значением переменной х становится ссылка на объект, заданный выражением е. Заметьте, текущим типом х становится тип объекта, заданного выражением е. Уже здесь проявляется одно из важных различий между классом и типом. Переменная, лучше сказать сущность х, согласно объявлению принадлежит классу Object, но ее тип — тип того объекта, с которым она связана в текущий момент, — может динамически изменяться.
Примеры преобразований
Перейдем к примерам. Класс Testing, содержащий примеры, представляет собой набор данных разного типа, над которыми выполняются операции, иллюстрирующие преобразования типов. Вот описание класса Testing;
using System;
namespace Types
{
/// <summary>
/// Класс Testing включает данные разных типов. Каждый его
/// открытый метод описывает некоторый пример,
/// демонстрирующий работу с типами.
/// Открытые методы могут вызывать закрытые методы класса.
/// </summary>
public class Testing
{
/// <summary>
/// набор скалярных данных разного типа.
/// </summary>
byte b = 255;
int x = 11;
uint ux = 1111;
float у = 5.5f;
double dy = 5.55;
string s = "Hello!";
string s1 = "25";
object obj = new Object ();
// Далее идут методы класса, приводимые по ходу
// описания примеров
}
В набор данных класса входят скалярные данные арифметического типа, относящиеся к значимым типам, переменные строкового типа и типа object, принадлежащие ссылочным типам. Рассмотрим закрытый (private) метод этого класса — процедуру WhoIsWho с формальным аргументом класса Object. Процедура выводит на консоль переданное ей имя аргумента, его тип и значение. Вот ее текст:
/// <summary>
/// Метод выводит на консоль информацию о типе и
/// значении фактического аргумента. Формальный
/// аргумент имеет тип object. Фактический аргумент
/// может иметь любой тип, поскольку всегда
/// допустимо неявное преобразование в тип object.
/// </summary>
/// <param name="name"> — Имя второго аргумента</раrаm>
/// <param name="any"> — Допустим аргумент любого типа</раrаm>
void WhoIsWho(string name, object any)
{
Console.WriteLine("type {0} is {1}, value is {2}",
name, any.GetType(), any.ToString ());
}
Вот открытый (public) метод класса Testing, в котором многократно вызывается метод WhoIsWho с аргументами разного типа:
/// <summary>
/// получаем информацию о типе и значении
/// переданного аргумента — переменной или выражения /// </summary>
public void WhoTest()
{
WhoIsWho("x",x);
WhoIsWho("ux",ux);
WhoIsWho("y",y);
WhoIsWho("dy",dy);
WhoIsWho ("s",s);
WhoIsWho("11 + 5.55 + 5.5f",11 + 5.55 + 5.5f);
obj = 11 + 5.55 + 5.5 f;
WhoIsWho("obj",obj);
}
Заметьте, сущность any — формальный аргумент класса Object при каждом вызове — динамически изменяет тип, связываясь с объектом, заданным фактическим аргументом. Поэтому тип аргумента, выдаваемый на консоль, — это тип фактического аргумента. Заметьте также, что наследуемый от класса Object метод GetType возвращает тип FCL, то есть тот тип, на который отражается тип языка и с которым реально идет работа при выполнении модуля. В большинстве вызовов фактическим аргументом является переменная — соответствующее свойство класса Testing, но в одном случае передается обычное арифметическое выражение, автоматически преобразуемое в объект. Аналогичная ситуация имеет место и при выполнении присваивания в рассматриваемой процедуре.