Использование классов Employee и Manager
Теперь, когда завершено определение классов Employee и Manager, напишем код, который их использует. Фактически, если загрузить исходный код этого проекта с web-сайта издательства Wrox press, то можно выяснить, что два эти класса определены как часть стандартного проекта форм Windows, достаточно похожего на пример SampleRoot. В данном случае, однако, основная форма имеет только один элемент управления — поле списка. Мы используем конструктор класса основной формы (класса с именем MainForm) для создания экземпляров объектов Employee и Manager, а затем выводим данные этих объектов в поле списка. Результат представлен ниже:
Код, используемый для создания этого вывода, выглядит следующим образом:
public MainForm() {
InitializeComponent();
Employee Britney = new Employee("Britney Spearse", 20000.00M);
Employee Elton = new Manager("Elton John", 50000.00M);
Manager Ginder = new Hanager("Geri Halliwell", 50000.00M, 20000.00M);
this.listBox1.Items.Add("Elton's name is $" + Elton.Name);
this.listBox1.Items.Add("Elton's salary is $" + Elton.Salary);
this.listBox1.Items.Add("Elton's bonus is " + ((Manager)Elton).Bonus);
this.listBox1.Items.Add("Elton's monthly payment is $" + Elton.GetMonthlyPayment());
this.listBox1.Items.Add("Elton's Company is " + Employee.CompanyName);
this.listBox1.Items.Add("Elton.ToString() : " + Elton.ToString());
this.listBox1.Items.Add("Britney.ToString(): " + Britney.ToString());
this.listBox1.Items.Add("Ginger.ToString(): " + Ginger.ToString());
}
Этот код должен быть вполне понятен, так как использует элементы C#, с которыми мы уже знакомы, за исключением одной небольшой странности — один из объектов Manager обозначен ссылкой Employee, а не ссылкой Manager. Мы объясним, как это работает, дальше.
Ссылки на производные классы
Подробнее рассмотрим класс Manager, на который ссылается переменная, объявленная как ссылка на Employee:
Employee Elton = new Manager("Elton John", 50000.00M);
Это на самом деле совершенно законный синтаксис C#. Правило вполне простое: если объявлена ссылка на некоторый тип данных В, то этой ссылке разрешается ссылаться на экземпляры В или экземпляры любого производного из В класса. Это работает, так как любой класс, производный из В, должен также реализовать все методы или свойства и т.д., которые реализует класс В. Поэтому в примере выше вызывается Elton.Name, Elton.Salary и Elton.GetMonthlyPayment(). Тот факт, что Employee реализует все эти члены, гарантирует, что любой класс, производный из Employee, также будет это делать. Поэтому не имеет значения, указывает ли ссылка на производный класс — мы по-прежнему сможем использовать эту ссылку для вызова любого члена класса, на который определена ссылка, и будем уверены, что этот метод существует в производном классе.
С другой стороны, отметим синтаксис, который использовался при вызове свойства Bonus на объекте Elton: ((Manager)Elton).Bonus. В этом случае необходимо явно преобразовать Elton в ссылку на Manager, так как Bonus не реализовано в Employee. Компилятор знает это и будет создавать ошибку компиляции, если попробовать вызвать Bonus через ссылку на Employee. Данная строка кода является на самом деле сокращением записи:
Manager ManagerElton = (Manager)Elton;
this.listBox1.Items.Add("Elton's bonus is " + ManagerElton.Bonus);
Как и в VB, преобразование между типами данных в C# называется преобразованием типов (casting). Можно заметить в приведенном выше коде, что синтаксис преобразования типов включает размещение имени типа данных в скобках перед именем переменной, преобразование которой собираются выполнить. Конечно, указанный объект должен содержать прежде всего правильный тип данных. Если в этом примере написать:
Manager ManagerBritney = (Manager)Britney;
то код будет компилироваться правильно, но при его работе будет получена ошибка, так как среда выполнения .NET определит, что Britney является только экземпляром Employee, а не Manager. Ссылкам разрешается ссылаться на экземпляры производных классов, но не на экземпляры базовых классов своего собственного типа. Не разрешается ссылке на Manager ссылаться на объект Employee. (Это недопустимо, так как подумайте, что произойдет, если попытаться вызвать свойство Bonus с помощью такой ссылки.)
Кстати, совершенно не рассматривались подробности возникновения ошибки во время выполнения. На самом деле C# имеет для такого случая очень развитый механизм, называемый исключениями, который кратко будет показан позже.
Так как VB не поддерживает наследование реализации, то не существует прямой параллели в VB для поддержки ссылок, указывающих на объекты производных классов, как в C#. Однако это напоминает VB — можно объявить ссылку на интерфейс, при этом не имеет значения, на какой тип объекта ссылается интерфейс, пока этот объект реализует интерфейс. Если бы классы Employee и Manager кодировались в VB, можно было вполне сделать так, определяя интерфейс IEmployee, который реализуют оба модуля классов, и затем обращаться к свойствам Employee через этот интерфейс.
Важным достоинством ссылок, способных указывать на экземпляры производных классов, является то, что можно формировать массивы объектных ссылок, где различные объекты массива имеют различные типы. Это аналогично ситуации в Visual Basic, где можно сформировать массив ссылок на интерфейсы и не беспокоиться о том факте, что эти интерфейсные ссылки реализуются совершенно различными классами объектов.
Мы не видели еще, как C# работает с массивами, поэтому перепишем код классов Employee и Manager, чтобы сформировать массив объектных ссылок. Этот пересмотренный код можно также загрузить с web-сайта издательства Wrox Press, как пример EmployeeMaragerWithArrays. Новый код выглядит следующим образом:
public MainForm() {
InitializeComponent();
Employee Britney = new Employee("Britney Spears", 20000.00M);
Employee Elton = new Manager("Elton John", 50000.00M);
Manager Ginger = new Manager("Geri Halliwell", 50000.00M, 20000.00M);
Employee[] Employees = new Employee[3];
Employees[0] = Britney;
Employees[1] = Elton;
Employees[2] = Ginger;
for (int I = 0; I < 3; I++) {
this.listBox1.Items.Add(Employees[I].Name);
this.listBox1.Items.Add(Employees[I].ToString());
this.listBox1.Items.Add("");
}
}
Мы вызываем свойство Name и метод ToString() каждого элемента массива. Выполнение кода создает следующий результат.
Приведенный код показывает, что C# при работе с массивами использует квадратные скобки. Это означает, что в отличие от VB, не существует опасности какой-либо путаницы между массивом и вызовом метода или функции. Синтаксис для объявления массива выглядит так:
Employee[] Employees = new Employee[3];
Мы видим, что массив переменных некоторого тип объявляют, помещая квадратные скобки после имени типа. Массив в C# всегда считается ссылочным объектом (даже если его элементы являются простыми типами, как int или double), поэтому на самом деле существует два этапа: объявление ссылки и создание экземпляра массива. Чтобы сделать это понятнее, разделим приведенную выше строку кода следующим образом:
Employee[] Employees;
Employees = new Employee[3];
He существует разницы между тем, что делается здесь и созданием экземпляров объектов, за исключением того, что используются квадратные скобки для указания, что это массив. Отметим также, что размер массива определяется, когда создается экземпляр объекта, сама ссылка не содержит данных о размере массива — только его размерность. Размерность определяется любым количеством запятых в объявлении массива, поэтому, например, если надо объявить двухмерный, 3×4 массив чисел типа double, можно написать:
double [,] DoubleArray = new double[3, 4];
Есть и другие несущественные различия в синтаксисе объявления массивов, но мы будем придерживаться приведенных здесь правил. Когда имеется массив, то значения его элементам присваиваются обычным образом. Отметим, однако, одно различие между C# и VB, состоящее в том, что C# всегда начинает с элемента с индексом 0. В VB имеется возможность изменить его на индекс 1, используя инструкцию Option Base. Также в VB можно определить любую нижнюю границу для любого конкретного массива. Но это свойство не добавляет на самом деле никаких преимуществ и может снизить производительность, так как это означает, что при любом доступе к элементу массива в VB, код должен выполнить дополнительную проверку, чтобы определить, какова нижняя граница массива. C# такое действие не поддерживает.
В приведенном выше коде после инициализации элементов массива мы перебираем их в цикле. Необычный синтаксис цикла for будет скоро рассмотрен.
Отметим, что поскольку массив был объявлен как массив объектов Employee, то можно получить доступ только к тем членам каждого объекта, которые определены для класса Employee. Если требуется доступ к свойству Bonus любого объекта массива, то необходимо сначала преобразовать соответствующую ссылку в ссылку Manager, что будет означать проверку того, что объект на самом деле является менеджером. Это не трудно сделать, но данный вопрос находится за пределами рассмотрения настоящего приложения.