Полное руководство. С# 4.0
Шрифт:
При выполнении этого кода получается следующий результат. Объект #0 типа MyClass Объект #1 типа MyClass Объект #2 типа MyClass Упаковка и распаковка
Как пояснялось выше, все типы в С#, включая и простые типы значений, являются производными от класса object. Следовательно, ссылкой типа object можно вос пользоваться для обращения к любому другому типу, в том числе и к типам значений. Когда ссылка на объект класса object используется для обращения к типу значения, то такой процесс называется упаковкой. Упаковка приводит к тому, что значение про стого типа сохраняется в экземпляре объекта, т.е. "упаковывается" в объекте, который затем используется как и любой другой объект. Но в любом случае упаковка происхо дит автоматически. Для этого достаточно присвоить значение переменной
Распаковка представляет собой процесс извлечения упакованного значения из объекта. Это делается с помощью явного приведения типа ссылки на объект класса object к соответствующему типу значения. Попытка распаковать объект в другой тип может привести к ошибке во время выполнения.
Ниже приведен простой пример, демонстрирующий упаковку и распаковку. // Простой пример упаковки и распаковки. using System; class BoxingDemo { static void Main { int x; object obj; х = 10; obj = x; // упаковать значение переменной х в объект int у = (int)obj; // распаковать значение из объекта, доступного по // ссылке obj, в переменную типа int Console.WriteLine(у); } }
В этом примере кода выводится значение 10. Обратите внимание на то, что значе ние переменной х упаковывается в объект простым его присваиванием переменной obj, ссылающейся на этот объект. А затем это значение извлекается из объекта, до ступного по его ссылке obj, и далее приводится к типу int.
Ниже приведен еще один, более интересный пример упаковки. В данном случае значение типа int передается в качестве аргумента методу Sqr, который, в свою очередь, принимает параметр типа object. // Пример упаковки при передаче значения методу. using System; class BoxingDemo { static void Main { int x; x = 10; Console.WriteLine("Значение x равно: " + x); // значение переменной x автоматически упаковывается // когда оно передается методу Sqr. х = BoxingDemo.Sqr(х); Console.WriteLine("Значение x в квадрате равно: " + х); } static int Sqr(object о) { return (int)о * (int)о; } }
Вот к какому результату приводит выполнение этого кода. Значение х равно: 10 Значение х в квадрате равно: 100
В данном примере значение переменной х автоматически упаковывается при пере даче методу Sqr.
Упаковка и распаковка позволяют полностью унифицировать систему типов в С#. Благодаря тому что все типы являются производными от класса object, ссылка на значение любого типа может быть просто присвоена переменной ссылочного типа object, а все остальное возьмут на себя упаковка и распаковка. Более того, методы класса object оказываются доступными всем типам, поскольку они являются про изводными от этого класса. В качестве примера рассмотрим довольно любопытную программу. // Благодаря упаковке становится возможным вызов методов по значению! using System; class MethOnValue { static void Main { Console.WriteLine(10.ToString); } }
В результате выполнения этой программы выводится значение 10. Дело в том, что метод ToString возвращает строковое представление объекта, для которого он вы зывается. В данном случае строковым представлением значения 10 как вызывающего объекта является само значение 10! Класс object как универсальный тип данных
Если object является базовым классом для всех остальных типов и упаковка зна чений простых типов происходит автоматически, то класс 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; // Сохранить значения типа double. for(int i=3; i < 6; i++) ga[i] = (double) i / 2; // Сохранить две строки, а также значения типа bool и char. ga[6] = "Привет"; ga[7] = true; ga[8] = 'X'; 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# мо жет быть в полной мере реализован главный принцип полиморфизма: один интер фейс — множество методов.