C# 4.0 полное руководство - 2011 - Герберт Шилдт
Шрифт:
Интервал:
Закладка:
Упаковка и распаковка позволяют полностью унифицировать систему типов в С#. Благодаря тому что все типы являются производными от класса object, ссылка на значение любого типа может быть просто присвоена переменной ссылочного типа object, а все остальное возьмут на себя упаковка и распаковка. Более того, методы класса object оказываются доступными всем типам, поскольку они являются производными от этого класса. В качестве примера рассмотрим довольно любопытную программу.
// Благодаря упаковке становится возможным вызов методов по значению! using System;
/
class MethOnValue { static void Main() {
Console.WriteLine(10.ToString() ) ;
}
}
В результате выполнения этой программы выводится значение 10. Дело в том, что метод ToString () возвращает строковое представление объекта, для которого он вызывается. В данном случае строковым представлением значения 10 как вызывающего объекта является само значение 10!
Класс object как универсальный тип данных
Если obj ect является базовым классом для всех остальных типов и упаковка значений простых типов происходит автоматически, то класс object можно вполне использовать в качестве "универсального" типа данных. Для примера рассмотрим программу, в которой сначала создается массив типа object, элементам которого затем присваиваются значения различных типов данных.
// Использовать класс object для создания массива "обобщенного" типа.
using System;
class GenericDemo { static void Main() {
object[] ga. = new object[10];
// Сохранить целые значения, for (int i=0; i < 3; i++) ga[i] = i;
ga[9] = "Конец";
for(int i = 0; i < ga.Length; i++)
Console.WriteLine("ga[" + i + "]: " + ga[i] + " ");
}
}
Выполнение этой программы приводит к следующему результату.
да [ 0] : 0 да[1] : 1
да
[2] :
2
да[3] :
1.5
да [4] :
2
да[5] :
2.5
да[6] :
Привет
да
[7] :
True
да
[8] :
X
да
[9] :
Конец
Как показывает данный пример, по ссылке на объект класса object можно обращаться к данным любого типа, поскольку в переменной ссылочного типа object допускается хранить ссылку на данные всех остальных типов. Следовательно, в массиве типа object из рассматриваемого здесь примера можно сохранить данные практически любого типа. В развитие этой идеи можно было бы, например, без особого труда создать класс стека со ссылками на объекты класса object. Это позволило бы хранить в стеке данные любого типа.
Несмотря на то что универсальный характер класса object может быть довольно эффективно использован в некоторых ситуациях, было бы ошибкой думать, что с помощью этого класса стоит пытаться обойти строго соблюдаемый в C# контроль типов. Вообще говоря, целое значение следует хранить в переменной типа int, строку — в переменной ссылочного типа string и т.д.
А самое главное, что начиная с версии 2.0 для программирования на C# стали доступными подлинно обобщенные типы данных — обобщения (более подробно они рассматриваются в главе 18). Внедрение обобщений позволило без труда определять классы и алгоритмы, автоматически обрабатывающие данные разных типов, соблюдая типовую безопасность. Благодаря обобщениям отпала необходимость пользоваться классом object как универсальным типом данных при создании нового кода..Универсальный характер этого класса лучше теперь оставить для применения в особых случаях.
ГЛАВА 12 Интерфейсы, структуры и перечисления
В этой главе рассматривается одно из самых важных в C# средств: интерфейс, определяющий ряд методов для реализации в классе. Но поскольку в самом интерфейсе ни один из методов не реализуется, интерфейс представляет собой чисто логическую конструкцию, описывающую функциональные возможности без конкретной их реализации.
Кроме того, в этой главе представлены еще два типа данных С#: структуры и перечисления. Структуры подобны классам, за исключением того, что они трактуются как типы значений, а не ссылочные типы. А перечисления представляют собой перечни целочисленных констант. Структуры и перечисления расширяют богатый арсенал средств программирования на С#.
Интерфейсы
Иногда в объектно-ориентированном программировании полезно определить, что именно должен делать класс, но не как он должен это делать. Примером тому может служить упоминавшийся ранее абстрактный метод.
В абстрактном методе определяются возвращаемый тип и сигнатура метода, но не предоставляется его реализация.
А в производном классе должна быть обеспечена своя собственная реализация каждого абстрактного метода, определенного в его базовом классе. Таким образом, абстрактный метод определяет интерфейс, но не реализацию метода. Конечно, абстрактные классы и методы приносят известную пользу, но положенный в их основу принцип может быть
развит далее. В C# предусмотрено разделение интерфейса класса и его реализации с помощью ключевого слова interface.
С точки зрения синтаксиса интерфейсы подобны абстрактным классам. Но в интерфейсе ни у одного из методов не должно быть тела. Это означает, что в интерфейсе вообще не предоставляется никакой реализации. В нем указывается только, что именно следует делать, но не как это делать. Как только интерфейс будет определен, он может быть реализован в любом количестве классов. Кроме того, в одном классе может быть реализовано любое количество интерфейсов.
Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса. Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному. Тем не менее в каждом из них должен поддерживаться один и тот же набор методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым. Благодаря поддержке интерфейсов в C# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.
Интерфейсы объявляются с помощью ключевого слова interface. Ниже приведена упрощенная форма объявления интерфейса.
interface имя{
возвращаемый_тип имя_метода1 (список_параметров) ; возвращаемый_тип ммя_метода2 [список_параметров) ;
// ...
возвращаемый_тип имя_методаЫ(список_параметров) ;
}
где имя — это конкретное имя интерфейса. В объявлении методов интерфейса используются только их возвращаемый_тип и сигнатура. Они, по существу, являются абстрактными методами. Как пояснялось выше, в интерфейсе не может быть никакой реализации. Поэтому все методы интерфейса должны быть реализованы в каждом классе, включающем в себя этот интерфейс. В самом же интерфейсе методы неявно считаются открытыми, поэтому доступ к ним не нужно указывать явно.
Ниже приведен пример объявления интерфейса для класса, генерирующего последовательный ряд чисел.
public interface ISeries {
int GetNextO; // возвратить следующее по порядку число void Reset(); // перезапустить
void SetStart(int х); // задать начальное значение
}
Этому интерфейсу присваивается имя ISeries. Префикс I в имени интерфейса указывать необязательно, но это принято делать в практике программирования, чтобы как-то отличать интерфейсы от классов. Интерфейс ISeries объявляется как public и поэтому может быть реализован в любом классе какой угодно программы.
Помимо методов, в интерфейсах можно также указывать свойства, индексаторы и события. Подробнее о событиях речь пойдет в главе 15, а в этой главе-основное внимание будет уделено методам, свойствам и индексаторам. Интерфейсы не могут содержать члены данных. В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как static.