Распаковка представляет собой процесс извлечения упакованного значения изобъекта. Это делается с помощью явного приведения типа ссылки на объект класса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# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.
Интерфейсы объявляются с помощью ключевого слова interface. Ниже приведена упрощенная форма объявления интерфейса.interface имя{ возвращаемый_тип имя_метода1(список_параметров); возвращаемый_тип имя_метода2(список_параметров); // ... возвращаемый_тип имя_методаN{список_параметров);}