Книга: Microsoft Visual C#. Подробное руководство. 8-е издание
Назад: 12. Работа с наследованием
Дальше: 14. Использование сборщика мусора и управление ресурсами

13. Создание интерфейсов и определение абстрактных классов

Прочитав эту главу, вы научитесь:

определять интерфейс, указывая сигнатуры и возвращаемые методами типы;

реализовывать интерфейс в структуре или классе;

ссылаться на класс через интерфейс;

фиксировать общие детали реализации в абстрактном классе;

реализовывать запечатанные классы, которые не могут использоваться для создания на их основе производных классов.

Наследование, осуществляемое из класса, является весьма эффективным механизмом, но реальная эффективность наследования проявляется при наследовании из интерфейса. Интерфейс не содержит никакого кода и никаких данных, в нем только определяются методы и свойства, которые должен предоставить класс, получающий наследование из интерфейса. Используя интерфейс, можно полностью отделить имена и сигнатуры методов класса от реализации методов.

Абстрактные классы во многом похожи на интерфейсы, за исключением того, что они могут содержать код и данные. Но конкретные методы абстрактного класса можно определить как виртуальные, чтобы класс, получающий наследование от абстрактного класса, мог дополнительно предоставить свою собственную реализацию этих методов. Абстрактные классы часто используются с интерфейсами, и вместе они предоставляют ключевую технологию, с помощью которой, как вы увидите в этой главе, можно создавать расширяемые среды программирования.

Основные сведения об интерфейсах

Предположим, что вам нужно определить новый класс, в котором можно хранить коллекцию объектов примерно так же, как и при использовании массива. Но, в отличие от массива, вам нужно предоставить метод по имени RetrieveInOrder, чтобы приложение могло извлекать объекты в последовательности, зависящей от типа объекта, содержащегося в коллекции. (При использовании обычного массива можно обойти все его содержимое, а по умолчанию элементы извлекаются по их индексам.) Если в коллекции содержатся буквенно-цифровые объекты, например строки, коллекция должна позволить приложению извлекать эти строки в последовательности, соответствующей последовательности упорядочения их компьютером, а если коллекция содержит числовые объекты, например целые числа, она должна позволить приложению извлекать эти объекты в цифровой последовательности.

При определении класса коллекции вам не хочется накладывать ограничение на типы объектов, которые могут в ней содержаться (объекты могут даже иметь типы классов или структур), следовательно, вы не будете знать, как упорядочить эти объекты. Как же тогда можно будет снабдить класс коллекции методом, сортирующим объекты тех типов, о которых вам ничего не известно при написании этого класса коллекции? На первый взгляд эта задача похожа на задачу с методом ToString, рассмотренную в главе 12 «Работа с наследованием», которая может быть решена путем объявления виртуального метода, который в свою очередь может быть переопределен подклассами вашего класса коллекции. Но любое сходство вводит в заблуждение. Отношений наследования между классом коллекции и содержащимися в нем объектами нет, поэтому пользы от виртуального метода не будет. Если вдуматься, то задача относится не к самой коллекции, а к способу упорядочения объектов в коллекции в зависимости от типа имеющегося в ней объекта. Решение заключается в требовании, чтобы всеми объектами предоставлялся метод, подобный методу CompareTo, показанному в следующем примере кода, который может вызываться методом коллекции RetrieveInOrder, открывая для коллекции возможность сравнивать объекты друг с другом:

int CompareTo(object obj)

{

    // возвращает нулевое значение, если данный экземпляр равен obj

    // возвращает значение меньше нуля, если данный экземпляр меньше, чем obj

    // возвращает значение больше нуля, если данный экземпляр больше, чем obj

    ...

}

Для собираемых в коллекцию объектов, включающих метод CompareTo, вы можете определить интерфейс и указать, что класс коллекции может содержать только классы, которые реализуют этот интерфейс. Таким образом, интерфейс аналогичен договору. Если класс реализует интерфейс, этот интерфейс гарантирует, что в классе содержатся все методы, указанные в интерфейсе. Этот механизм гарантирует возможность вызова метода CompareTo в отношении всех объектов в коллекции и их сортировки.

Используя интерфейсы, можно реально отделить понятие «что» от понятия «как». Интерфейс дает вам только имя, тип возвращаемого значения и параметры метода. А конкретная реализация метода в задачу интерфейса не входит. В интерфейсе дается описание той функциональной возможности, которую должен предоставить класс, но не способ ее реализации.

Определение интерфейса

Определение интерфейса синтаксически похоже на определение класса, за исключением того, что вместо ключевого слова class используется ключевое слово interface. Внутри интерфейса, точно так же, как внутри класса или структуры, объявляются методы, за исключением того, что для них никогда не указывается спецификатор доступа (public, private или protected). Кроме того, методы в интерфейсе не имеют реализации — они представляют собой простое объявление, и все типы, реализующие интерфейс, должны предоставлять для методов свои собственные реализации. Соответственно тело метода заменяется точкой с запятой, например:

interface IComparable

{

    int CompareTo(object obj);

}

175212.png

СОВЕТ В документации по среде Microsoft .NET рекомендуется давать интерфейсам имена, начинающиеся с прописной буквы I. Это соглашение является пережитком венгерской нотации в C#. Кстати, только что показанный интерфейс IComparable уже определен в пространстве имен System.

Интерфейс не может содержать данные, то есть вы не можете добавлять к интерфейсу поля, даже закрытые.

Реализация интерфейса

Чтобы реализовать интерфейс, нужно объявить класс или структуру, наследующую интерфейс, а затем реализовать все указанные интерфейсом методы. Это не наследование как таковое, хотя синтаксис точно такой же, и как вы увидите далее в этой главе, некоторые семантические элементы имеют многие отличительные признаки наследования. Следует заметить, что, в отличие от наследования класса, интерфейс может быть реализован в структуре.

Предположим, к примеру, что вы определяете Mammal-иерархию, рассмотренную в главе 12, но при этом вам нужно указать млекопитающих, передвигающихся по суше (land-bound), предоставив метод по имени NumberOfLegs, который возвращает int-значение с количеством имеющихся у млекопитающего ног (или лап). (Этот интерфейс у передвигающихся по суше млекопитающих не реализован.) Интерфейс ILandBound, в котором содержится этот метод, можно определить следующим образом:

interface ILandBound

{

    int NumberOfLegs();

}

Затем интерфейс можно реализовать в классе Horse. При наследовании интерфейса каждому определенному в нем методу предоставляется реализация (в данном случае имеется только один метод NumberOfLegs):

class Horse : ILandBound

{

    ...

    public int NumberOfLegs()

    {

        return 4;

    }

}

При реализации интерфейса нужно обеспечить точное совпадение каждого метода с соответствующим методом в интерфейсе, придерживаясь следующих правил:

• Имена и типы возвращаемых значений должны точно совпадать.

• Все параметры, включая модификаторы ref и out, должны точно совпадать.

• Все методы, реализующие интерфейс, должны находиться в открытом доступе. Но если используется явная реализация интерфейса, метод не должен иметь спецификатор доступа.

Если между интерфейсным определением и объявленной реализацией будут какие-нибудь различия, класс не пройдет компиляцию.

175217.png

СОВЕТ Интегрированная среда разработки (IDE) Microsoft Visual Studio может способствовать сокращению числа ошибок в программе, вызванных неподобающей реализацией методов, объявленных в интерфейсе. Мастер реализации интерфейсов Implement Interface может создавать заглушки для каждого элемента интерфейса, реализуемого классом. Затем эти заглушки могут наполняться соответствующим кодом. Порядок использования этого мастера будет показан в следующих упражнениях.

Класс может быть наследником другого класса и наряду с этим реализовывать интерфейс. В таком случае в C# не делается различий между базовым классом и интерфейсом путем использования специального ключевого слова as, как это, к примеру, делается в Java. Вместо этого в C# используется позиционная система записи. Сначала всегда указывается имя базового класса, затем стоит запятая, после которой указывается интерфейс. В следующем примере определяется класс Horse, являющийся производным от класса Mammal, но дополнительно еще реализующий интерфейс ILandBound:

interface ILandBound

{

    ...

}

 

class Mammal

{

    ...

}

 

class Horse : Mammal , ILandBound

{

    ...

}

173802.png

ПРИМЕЧАНИЕ Интерфейс InterfaceA может быть наследником другого интерфейса, InterfaceB. С технической точки зрения это называется не наследованием, а расширением интерфейса. В данном случае любой класс или структура, реализующие InterfaceA, должны предоставлять реализацию всех методов, имеющихся как в InterfaceB, так и в InterfaceA.

Ссылка на класс через его интерфейс

Точно так же, как вы можете сослаться на объект путем использования переменной, определенной как класс, стоящий выше в иерархии, вы можете сослаться на объект путем использования переменной, определенной как интерфейс, реализуемый классом объекта. Если взять предыдущий пример, то на Horse-объект можно сослаться с помощью ILandBound-переменной:

Horse myHorse = new Horse(...);

ILandBound iMyHorse = myHorse; // это вполне допустимо

Ссылка работает, поскольку все лошади (horses) являются млекопитающими, передвигающимися по суше (land-bound mammals), хотя обратное утверждение неверно — вы не можете присвоить ILandBound-объект Horse-переменной без предварительного приведения к типу, чтобы проверить, что она действительно ссылается на Horse-объект, а не на какой-нибудь другой класс, в котором также реализуется интерфейс ILandBound.

Польза от ссылки на объект через интерфейс заключается в том, что появляется возможность использования ее для определения методов, способных принимать в качестве параметров различные типы, при условии, что в типах реализован указанный интерфейс. Например, показанный далее метод FindLandSpeed может принять любой аргумент, реализующий интерфейс ILandBound:

int FindLandSpeed(ILandBound landBoundMammal)

{

    ...

}

Вы можете проверить, что объект является экземпляром класса, реализующего конкретный интерфейс, воспользовавшись оператором is, показанным в главе 8 «Основные сведения о значениях и ссылках». Оператор is используется для определения того, имеет ли объект указанный тип, и работает с интерфейсами точно так же, как с классами и структурами. Например, следующий блок кода перед тем, как присвоить переменную myHorse переменной типа ILandBoun, проверяет факт реализации в переменной myHorse интерфейса ILandBound:

if (myHorse is ILandBound)

{

    ILandBound iLandBoundAnimal = myHorse;

}

Учтите, что при ссылке на объект через интерфейс можно вызвать только те методы, которые видны через этот интерфейс.

Работа с несколькими интерфейсами

У класса может быть не более одного базового класса, но в нем допускается реализация неограниченного количества интерфейсов. Класс должен реализовать все методы, объявленные этими интерфейсами.

Если в структуре или классе реализуется более одного интерфейса, то интерфейсы указываются в списке через запятую. Если у класса имеется также базовый класс, интерфейсы перечисляются после базового класса. Предположим, к примеру, что вы определили еще один интерфейс по имени IGrazable (травоядные), содержащий метод ChewGrass (жевать траву) для всех травоядных животных. Тогда класс Horse можно определить следующим образом:

class Horse : Mammal, ILandBound, IGrazable

{

    ...

}

Явная реализация интерфейса

Приводимые до сих пор примеры показывали классы, реализующие интерфейс неявным образом. Если еще раз посмотреть на интерфейс ILandBound и на класс Horse, показанные далее, то можно увидеть: хотя класс Horse реализует интерфейс ILandBound, ничего в реализации метода NumberOfLegs не говорит о том, что он является частью интерфейса ILandBound:

interface ILandBound

{

    int NumberOfLegs();

}

 

class Horse : ILandBound

{

    ...

    public int NumberOfLegs()

    {

        return 4;

    }

}

В простой ситуации все может обойтись без каких-либо осложнений, но представим себе, что класс Horse реализует сразу несколько интерфейсов. Ничто не мешает указать метод точно с таким же именем, хотя у методов может быть различная семантика. Предположим, к примеру, что вам нужно реализовать транспортную систему на основе конных экипажей. Длинный путь может быть разбит на несколько этапов, обозначаемых термином leg. Если нужно отслеживать, сколько этапов каждая лошадь тянула за собой экипаж, может быть определен следующий интерфейс:

interface IJourney

{

    int NumberOfLegs();

}

Теперь, если реализовать этот интерфейс в классе Horse, возникнет весьма интересная проблема:

class Horse : ILandBound, IJourney

{

    ...

    public int NumberOfLegs()

    {

        return 4;

    }

}

Это вполне допустимый код, но что он означает — что у лошади четыре ноги или что она тянула за собой экипаж четыре этапа пути? Ответ таков: и то и это! Изначально C# не различает, какой из интерфейсов реализует данный метод, поэтому один и тот же метод фактически реализует оба интерфейса.

Для решения данной проблемы и устранения неоднозначности при определении того, какой из методов является частью реализации того или иного интерфейса, можно реализовать интерфейс явным образом. Для этого при реализации метода нужно указать, какому интерфейсу он принадлежит:

class Horse : ILandBound, IJourney

{

    ...

    int ILandBound.NumberOfLegs()

    {

        return 4;

    }

    int IJourney.NumberOfLegs()

    {

        return 3;

    }

}

Теперь можно увидеть, что у лошади четыре ноги и она тянула за собой экипаж на протяжении трех этапов пути.

Кроме указания в имени метода префикса в виде имени интерфейса, в этом синтаксисе присутствует еще одна особенность: методы не помечаются как открытые (public). Для методов, являющихся частью явной реализации интерфейса, можно указать защиту. Это приводит к еще одному весьма интересному явлению. Если создать Horse-переменную в коде, то вы фактически не можете вызвать ни один из методов NumberOfLegs, поскольку они вне области видимости. Дело в том, что класс Horse считает их оба закрытыми. В этом есть вполне определенный смысл. Если бы методы были видны через класс Horse, то какой из методов будет вызван следующим кодом: тот, что реализован для интерфейса ILandBound, или тот, что предназначен для интерфейса IJourney?

Horse horse = new Horse();

...

// Следующая инструкция не будет скомпилирована

int legs = horse.NumberOfLegs();

Так как же получить доступ к этим методам? Нужно сослаться на Horse-объект через соответствующий интерфейс:

Horse horse = new Horse();

...

IJourney journeyHorse = horse;

int legsInJourney = journeyHorse.NumberOfLegs();

ILandBound landBoundHorse = horse;

int legsOnHorse = landBoundHorse.NumberOfLegs();

Я рекомендую осуществлять по возможности явную реализацию интерфейсов.

Ограничения, накладываемые на интерфейсы

Важно помнить, что интерфейс никогда не содержит никакой реализации. Из этого вполне естественно вытекают следующие ограничения.

• В интерфейсе не разрешается определять какие-либо поля, даже статические. Поле является особенностью реализации класса или структуры.

• В интерфейсе не разрешается определять какие-либо конструкторы. Конструктор также рассматривается в качестве особенности реализации класса или структуры.

• В интерфейсе не разрешается определять деструктор. В деструкторе содержатся инструкции, используемые для уничтожения объекта, являющегося экземпляром класса. (Деструкторы рассматриваются в главе 14 «Использование сборщика мусора и управление ресурсами».)

• Для метода нельзя указывать модификатор доступа. Все методы в интерфейсе подразумеваются открытыми.

• В интерфейс нельзя вкладывать какие-либо типы (например, перечисления, структуры, классы или интерфейсы).

• Интерфейс не может что-либо наследовать от структуры или класса, хотя интерфейс может быть наследником другого интерфейса. В структурах и классах содержится реализация, и если бы интерфейсу разрешалось быть наследником кого-либо из них, то он унаследовал бы какую-нибудь реализацию.

Определение и использование интерфейсов

В следующих упражнениях вы дадите определение и реализуете интерфейсы, являющиеся составной частью простого графического пакета для рисования. Будут определены два интерфейса с именами IDraw и IColor, а затем вы определите классы, реализующие эти интерфейсы. В каждом классе будет определена фигура, которую можно будет нарисовать на холсте, расположенном в форме (холст является элементом управления, которым можно воспользоваться для рисования линий, текста и фигур на экране).

В интерфейсе IDraw определяются следующие методы:

• SetLocation — с его помощью можно указать для фигуры на холсте позицию в виде координат x и y;

• Draw — рисует фигуру на холсте в той позиции, которая указана с помощью метода SetLocation.

В интерфейсе IColor определяется метод SetColor. Он используется для указания цвета фигуры. Когда фигура рисуется на холсте, она будет появляться в этом цвете.

Определение интерфейсов IDraw и IColor

Откройте в среде Microsoft Visual Studio 2015 проект Drawing, который находится в папке \Microsoft Press\VCSBS\Chapter 13\Drawing вашей папки документов.

Проект Drawing является графическим приложением. В нем имеется форма по имени DrawingPad. В этой форме содержится элемент управления canvas (холст) по имени drawingCanvas. Эта форма и холст будут использоваться для тестирования вашего кода.

Щелкните в обозревателе решений на проекте Drawing. Щелкните в меню Проект на пункте Добавить новый элемент. Откроется диалоговое окно Добавить новый элемент — Drawing, в левой панели которого щелкните на пункте Visual C#, а затем на пункте Код. В средней панели щелкните на названии шаблона Интерфейс. Наберите в поле Имя строку IDraw.cs, после чего щелкните на кнопке Добавить. Среда Visual Studio создаст файл IDraw.cs и добавит его к вашему проекту. Содержимое IDraw.cs появится в окне редактора и будет иметь следующий вид:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace Drawing

{

    interface IDraw

    {

    }

}

Добавьте к списку в самом начале файла IDraw.cs следующую директиву using:

using Windows.UI.Xaml.Controls;

В этом интерфейсе вы будете ссылаться на класс Canvas, который находится в пространстве имен Windows.UI.Xaml.Controls для приложений универсальной платформы Windows (Universal Windows Platform (UWP)).

Добавьте к определению интерфейса IDraw методы, выделенные жирным шрифтом:

interface IDraw

{

    void SetLocation(int xCoord, int yCoord);

    void Draw(Canvas canvas);

}

Щелкните еще раз в меню Проект на пункте Добавить новый элемент. Щелкните в средней панели появившегося диалогового окна на названии шаблона Интерфейс. Наберите в поле Имя строку IColor.cs, после чего щелкните на кнопке Добавить. Среда Visual Studio создаст файл IColor.cs и добавит его к вашему проекту. Содержимое IColor.cs появится в окне редактора. Добавьте к списку в самом начале файла IColor.cs следующую директиву using:

using Windows.UI;

В этом интерфейсе вы будете ссылаться на класс Color, который находится в пространстве имен Windows.UI для UWP-приложений.

Добавьте к определению интерфейса IColor метод, выделенный здесь жирным шрифтом:

interface IColor

{

    void SetColor(Color color);

}

Теперь вы определили интерфейсы IDraw и IColor. На следующем этапе для реализации этих интерфейсов будут созданы несколько классов. В следующем упражнении вы создадите два новых класса фигур с именами Square и Circle. Оба интерфейса будут реализованы именно в этих классах.

Создание классов Square и Circle и реализация интерфейсов

Щелкните в меню Project на пункте Добавить класс. Убедитесь, что в средней панели в диалоговом окне Добавить новый элемент — Drawing выбран шаблон Класс. В поле Имя наберите строку Square.cs, после чего щелкните на кнопке Добавить. Среда Visual Studio создаст файл Square.cs и выведет его в окно редактора.

Добавьте к списку в верхней части файла Square.cs следующие директивы:

using Windows.UI;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Shapes;

using Windows.UI.Xaml.Controls;

Измените определение класса Square (квадрат), указав выделенную жирным шрифтом настройку на реализацию интерфейсов IDraw и IColor:

class Square : IDraw, IColor

{

}

Добавьте в класс Square следующие закрытые переменные, выделенные здесь жирным шрифтом:

class Square : IDraw, IColor

{

    private int sideLength;

    private int locX = 0, locY = 0;

    private Rectangle rect = null;

}

В этих переменных будут храниться позиция и размер Square-объекта на холсте. Класс Rectangle находится в пространстве имен Windows.UI.Xaml.Shapes для UWP-приложений. Этот класс будет использоваться для рисования прямоугольника. Добавьте к классу Square конструктор, выделенный жирным шрифтом:

class Square : IDraw, IColor

{

    ...

    public Square(int sideLength)

    {

        this.sideLength = sideLength;

    }

}

Конструктор инициализирует поле sideLength и указывает каждую сторону квадрата.

В определении класса Square наведите указатель мыши на интерфейс IDraw. В появившемся контекстном меню со значком горящей лампочки щелкните на пункте Реализовать интерфейс явно (рис. 13.1).

13_01.tif 

Рис. 13.1

Это заставит Visual Studio создать исходную реализацию методов в интер­фейсе IDraw. Если есть такое желание, то методы к классу Square можно добавить вручную. Код, созданный средой Visual Studio, показан в следующем примере:

void IDraw.SetLocation(int xCoord, int yCoord)

{

    throw new NotImplementedException();

}

 

void IDraw.Draw(Canvas canvas)

{

    throw new NotImplementedException();

}

На данный момент каждый из этих методов выдает исключение NotImplemented­Exception. Ожидается, что вы замените тела этих методов собственным кодом.

Замените код, имеющийся в методе IDraw.SetLocation, который выдает исключение NotImplementedException, следующими инструкциями, выделенными жирным шрифтом:

void IDraw.SetLocation(int xCoord, int yCoord)

{

    this.locX = xCoord;

    this.locY = yCoord;

}

Этот код сохраняет переданные через параметры значения в полях locX и locY объекта Square.

Замените код, имеющийся в методе IDraw.Draw, следующими инструкциями, выделенными жирным шрифтом:

void IDraw.Draw(Canvas canvas)

{

    if (this.rect != null)

    {

        canvas.Children.Remove(this.rect);

}

    else

    {

        this.rect = new Rectangle();

    }

    this.rect.Height = this.sideLength;

    this.rect.Width = this.sideLength;

    Canvas.SetTop(this.rect, this.locY);

    Canvas.SetLeft(this.rect, this.locX);

    canvas.Children.Add(this.rect);

}

Этот метод выводит Square-объект путем рисования на холсте Rectangle-фигуры. (Квадрат (square) — это просто равносторонний прямоугольник.) Если Rectangle уже был ранее нарисован (возможно, в другом месте и другим цветом), он удаляется с холста. Высота (height) и ширина (width) прямоугольника Rectangle устанавливаются путем использования значения поля sideLength. Позиция Rectangle на холсте устанавливается с помощью статических методов SetTop и SetLeft, принадлежащих классу Canvas, а затем Rectangle добавляется к холсту. (Это приводит к его отображению на холсте.)

Добавьте к классу Square метод SetColor из интерфейса IColor:

void IColor.SetColor(Color color)

{

    if (this.rect != null)

    {

        SolidColorBrush brush = new SolidColorBrush(color);

        this.rect.Fill = brush;

    }

}

Этот метод проверяет, выведен ли объект Square на экран. (Поле rect будет иметь null-значение, если прямоугольник еще не появился на экране.) Код устанавливает для свойства Fill поля rect конкретный цвет, для чего используется SolidColorBrush-объект. (Подробности работы класса SolidColorBrush нас в данном случае не интересуют.)

Щелкните в меню Проект на пункте Добавить класс. Наберите в поле Имя появившегося диалогового окна Добавить новый элемент — Drawing строку Circle.cs, после чего щелкните на кнопке Добавить. Среда Visual Studio создаст файл Circle.cs и выведет его содержимое в окно редактора.

Добавьте к списку в верхней части файла Circle.cs следующие директивы using:

using Windows.UI;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Shapes;

using Windows.UI.Xaml.Controls;

Измените определение класса Circle, чтобы в нем появилось указание на реализацию интерфейсов IDraw и IColor, выделенное здесь жирным шрифтом:

class Circle : IDraw, IColor

{

}

Добавьте к классу Circle следующие закрытые переменные, выделенные жирным шрифтом:

class Circle : IDraw, IColor

{

    private int diameter;

    private int locX = 0, locY = 0;

    private Ellipse circle = null;

}

В этих переменных будут храниться позиция и размер Circle-объекта на холсте. Класс Ellipse предоставляет функциональные возможности, которые будут использоваться вами для рисования круга.

Добавьте к классу Circle конструктор, выделенный здесь жирным шрифтом:

class Circle : IDraw, IColor

{

    ...

    public Circle(int diameter)

    {

        this.diameter = diameter;

    }

}

Этот конструктор инициализирует поле diameter.

Добавьте к классу Circle следующий метод SetLocation:

void IDraw.SetLocation(int xCoord, int yCoord)

{

    this.locX = xCoord;

    this.locY = yCoord;

}

Этот метод реализует часть интерфейса IDraw и использует такой же код, как и в классе Square.

Добавьте к классу Circle показанный далее метод Draw:

void IDraw.Draw(Canvas canvas)

{

    if (this.circle != null)

    {

        canvas.Children.Remove(this.circle);

    }

    else

    {

        this.circle = new Ellipse();

    }

 

    this.circle.Height = this.diameter;

    this.circle.Width = this.diameter;

    Canvas.SetTop(this.circle, this.locY);

    Canvas.SetLeft(this.circle, this.locX);

    canvas.Children.Add(this.circle);

}

Этот метод также является частью интерфейса IDraw. Он аналогичен методу Draw в классе Square, за исключением того, что выводит Circle-объект путем рисования на холсте Ellipse-фигуры. (Круг является эллипсом с одинаковыми значениями высоты и ширины.)

Добавьте к классу Circle следующий метод SetColor:

void IColor.SetColor(Color color)

{

    if (this.circle != null)

    {

        SolidColorBrush brush = new SolidColorBrush(color);

        this.circle.Fill = brush;

    }

}

Этот метод является частью интерфейса IColor. Как и прежде, этот метод похож на тот, что принадлежит классу Square.

Работа с классами Square и Circle завершена, и теперь можно воспользоваться формой для их тестирования.

Тестирование классов Square и Circle

Выведите в окно конструктора файл DrawingPad.xaml. Щелкните в форме на большой затененной области, которая является в ней Canvas-объектом, в результате чего на нем установится фокус. Щелкните в окне Свойства на кнопке Обработчики событий для выбранного элемента. (Значок на этой кнопке похож на разряд молнии.)

Найдите в списке событий Tapped и дважды щелкните на его текстовом поле. Среда Visual Studio создаст метод по имени drawingCanvas_Tapped для класса DrawingPad и выведет его в окно редактора. Это обработчик события, запускаемый при прикосновении пользователя пальцем к холсту или при щелчке левой кнопкой мыши, когда указатель находится над холстом. Более подробно обработчики событий рассматриваются в главе 20 «Отделение логики приложения и обработка событий».

Добавьте к списку в начале файла DrawingPad.xaml.cs следующую директиву using:

using Windows.UI;

В пространстве имен Windows.UI содержится определение класса Colors, который будет использоваться при установке цвета фигуры в ходе ее рисования.

Добавьте к методу drawingCanvas_Tapped следующий код, выделенный жирным шрифтом:

private void drawingCanvas_Tapped(object sender, TappedRoutedEventArgs e)

{

    Point mouseLocation = e.GetPosition(this.drawingCanvas);

    Square mySquare = new Square(100);

 

    if (mySquare is IDraw)

    {

        IDraw drawSquare = mySquare;

        drawSquare.SetLocation((int)mouseLocation.X, (int)mouseLocation.Y);

        drawSquare.Draw(drawingCanvas);

    }

}

Параметр этого метода TappedRoutedEventArgs предоставляет полезную информацию о позиции указателя мыши. В частности, метод GetPosition возвращает структуру Point, содержащую координаты x и y указателя мыши. Добавленный вами код создает новый Square-объект. Затем он проверяет, реализован ли в этом объекте интерфейс IDraw (это обычная практика, помогающая убедиться, что код не даст сбой во время выполнения при попытке сослаться на объект через нереализованный интерфейс), и создает ссылку на объект с использованием этого интерфейса. Следует напомнить, что при явной реализации интерфейса определенные в интерфейсе методы доступны только при создании ссылки на этот интерфейс. (Методы SetLocation и Draw по отношению к классу Square являются закрытыми и доступны только через интерфейс IDraw.) Затем код устанавливает размещение Square в позиции пальца пользователя или указателя мыши. Следует заметить, что координаты x и y в структуре Point фактически являются целочисленными значениями с двойной точностью (double), поэтому данный код приводит их к целочисленному типу (int). Затем код вызывает метод Draw для отображения Square-объекта.

Добавьте в конце метода drawingCanvas_Tapped следующий код, выделенный жирным шрифтом:

private void drawingCanvas_Tapped(object sender, TappedRoutedEventArgs e)

{

    ...

    if (mySquare is IColor)

    {

        IColor colorSquare = mySquare;

        colorSquare.SetColor(Colors.BlueViolet);

    }

}

Этот код тестирует класс Square на реализацию в нем интерфейса IColor: если интерфейс реализован, код создает ссылку на класс Square через этот интерфейс и вызывает метод SetColor для установки цвета Square-объекта на Colors.BlueViolet.

174890.png

ВНИМАНИЕ Перед тем как вызвать SetColor, нужно вызвать Draw, потому что метод SetColor устанавливает цвет для Square-объекта, только если он уже выведен на холст. Если вызвать SetColor до вызова Draw, цвет установлен не будет и Square-объект не появится.

Вернитесь к файлу DrawingPad.xaml в окне конструктора и щелкните на объекте Canvas.

Найдите в списке событий RightTapped, а затем дважды щелкните на текстовом поле с именем этого события.

Это событие возникает в случае, когда пользователь пальцем прикасается к холсту, некоторое время удерживает на нем палец, а затем убирает его с холста или же когда щелкает на холсте правой кнопкой мыши.

Добавьте к методу drawingCanvas_RightTapped следующий код, выделенный жирным шрифтом:

private void drawingCanvas_RightTapped(object sender,

                           RightTappedRoutedEventArgs e)

{

    Point mouseLocation = e.GetPosition(this.drawingCanvas);

    Circle myCircle = new Circle(100);

 

    if (myCircle is IDraw)

    {

        IDraw drawCircle = myCircle;

        drawCircle.SetLocation((int)mouseLocation.X, (int)mouseLocation.Y);

        drawCircle.Draw(drawingCanvas);

    }

 

    if (myCircle is IColor)

    {

        IColor colorCircle = myCircle;

        colorCircle.SetColor(Colors.HotPink);

    }

}

Логика, лежащая в основе этого кода, аналогична той логике, которая использовалась в методе drawingCanvas_Tapped, за исключением того, что этот код рисует и заполняет цветом круг, а не квадрат.

Щелкните в меню Отладка на пункте Начать отладку, чтобы выполнить сборку и запуск приложения.

Когда откроется окно Drawing Pad, прикоснитесь пальцем к любому месту холста, показанного в окне, или щелкните над этим местом мышью. Должен появиться фиолетовый квадрат.

Прикоснитесь к любому месту холста, некоторое время удерживайте на нем палец, после чего уберите его с холста или же щелкните правой кнопкой мыши над любым местом холста. Должен появиться розовый круг. Можно щелкать левой и правой кнопками мыши любое количество раз, и при каждом щелчке в позиции указателя мыши на холсте будет появляться квадрат или круг. На рис. 13.2 показано приложение, работающее под управлением Windows 10.

Вернитесь в среду Visual Studio и остановите отладку.

13_02.tif 

Рис. 13.2

Абстрактные классы

Интерфейсы ILandBound и IGrazable, рассмотренные в ранее представленном наборе упражнений, могут быть реализованы в множестве различных классов в зависимости от того, сколько различных типов млекопитающих нужно будет смоделировать в C#-приложении. В подобных этим ситуациях фрагменты производных классов довольно часто используют одну и ту же реализацию. Например, в следующих двух классах код явно продублирован:

class Horse : Mammal, ILandBound, IGrazable

{

    ...

    void IGrazable.ChewGrass()

    {

        Console.WriteLine("Chewing grass");

        // код для травоядных

    }

}

class Sheep : Mammal, ILandBound, IGrazable

{

    ...

    void IGrazable.ChewGrass()

    {

        Console.WriteLine("Chewing grass");

        // код для такого же травоядного, что и лошадь

    }

}

Повторение кода должно вас настораживать. Нужно по возможности реорганизовать код, избавившись от повторений и сократив затраты на его сопровождение. Один из способов выполнения этой задачи заключается в помещении общей реализации в новый класс, созданный специально для этой цели. В результате, как показано в следующем примере кода, новый класс можно будет поместить в иерархию классов:

class GrazingMammal : Mammal, IGrazable

{

    ...

    void IGrazable.ChewGrass()

    {

        // общий код для травоядных

        Console.WriteLine("Chewing grass");

    }

}

 

class Horse : GrazingMammal, ILandBound

{

    ...

}

 

class Sheep : GrazingMammal, ILandBound

{

    ...

}

Решение неплохое, но есть в нем одна загвоздка: фактически ничто не препятствует созданию экземпляров класса GrazingMammal (а заодно и класса Mammal). Но на самом деле в этом нет никакого смысла. Существование класса GrazingMammal обусловлено предоставлением общей исходной реализации. Он предназначен лишь для того, чтобы быть классом для наследования. Класс GrazingMammal является абстракцией общей функциональности, а не полноценным созданием.

Чтобы указать на невозможность создания экземпляров класса, можно объявить класс абстрактным, воспользовавшись для этого ключевым словом abstract:

abstract class GrazingMammal : Mammal, IGrazable

{

    ...

}

Если теперь попытаться создать в качестве его экземпляра GrazingMammal-объект, код не пройдет компиляцию:

GrazingMammal myGrazingMammal = new GrazingMammal(...); // недопустимый код

Абстрактные методы

В абстрактном классе могут содержаться абстрактные методы. В принципе, абстрактный метод похож на виртуальный (рассмотрен в главе 12), за исключением того, что в нем отсутствует тело метода. Этот метод должен быть перегружен производным классом. Абстрактный метод не может быть закрытым. В следующем примере в классе GrazingMammal в качестве абстрактного определяется метод DigestGrass: у классов травоядных млекопитающих должен использоваться такой же код для жевания травы, но у них должна быть собственная реализация метода DigestGrass. Польза от абстрактного метода просматривается в том случае, если его исходная реализации в абстрактном классе не имеет смысла, но при этом нужны гарантии того, что класс-наследник предоставит собственную реализацию этого метода.

abstract class GrazingMammal : Mammal, IGrazable

{

    public abstract void DigestGrass();

    ...

}

Запечатанные классы

Бывает, что применение наследования дается непросто и требует особой преду­смотрительности. При создании интерфейса или абстрактного класса вы заведомо пишете код, предназначенный для будущего наследования. Беда в том, что предсказать будущее нелегко. С приобретением опыта и практических навыков можно достичь мастерства в изготовлении гибкой, простой в использовании иерархии интерфейсов, абстрактных и обычных классов, но это требует немалых усилий, а кроме того, нужно иметь весьма четкое представление о моделируемой задаче. Иными словами, пока вы преднамеренно не спроектируете класс с прицелом на использование его в качестве базового, крайне маловероятно, что он будет хорошо работать именно в этом качестве. Программируя на C#, можно воспользоваться ключевым словом sealed (запечатанный), воспрепятствовав тем самым использованию класса в качестве базового, если будет принято соответствующее решение, например:

sealed class Horse : GrazingMammal, ILandBound

{

    ...

}

Если какой-либо класс попытается использовать Horse в качестве базового класса, то во время компиляции будет выдана ошибка. Следует учесть, что в запечатанном классе невозможно объявлять какие-либо виртуальные методы и что абстрактные классы не могут быть запечатанными.

Запечатанные методы

Ключевое слово sealed можно также использовать для объявления того, что отдельный метод в незапечатанном классе является запечатанным. Это означает, что производный класс не может перегрузить данный метод. Запечатать можно только тот метод, который объявлен с ключевым словом overrid, и метод объявляется как sealed override. О ключевых словах interface, virtual, override и sealed можно составить следующее представление.

• Интерфейс вводит в обращение имя метода.

• Виртуальный метод является первой реализацией метода.

• Переопределенный метод является еще одной реализацией метода.

• Запечатанный метод является последней реализацией метода.

Реализация и использование абстрактного класса

В следующих упражнениях абстрактный класс используется для рационализации кода, разработанного в предыдущем упражнении. В классах Square и Circle содержится довольно много повторяющегося кода. Этот код практичнее убрать в абстрактный класс по имени DrawingShape, поскольку это упростит сопровождение классов Square и Circle в будущем.

Создание абстрактного класса DrawingShape

Вернитесь в среду Visual Studio к проекту Drawing.

173810.png

ПРИМЕЧАНИЕ Окончательный рабочий вариант кода предыдущего упражнения можно получить в проекте Drawing, который находится в папке \Microsoft Press\VCSBS\Chapter 13\Drawing Using Interfaces вашей папки документов.

Щелкните в обозревателе решений на проекте Drawing, находящемся в одноименном решении. Щелкните в меню Проект на пункте Добавить класс. Откроется диалоговое окно Добавить новый элемент — Drawing. Наберите в поле Имя строку DrawingShape.cs, после чего щелкните на кнопке Добавить. Среда Visual Studio создаст класс и выведет его в окно редактора.

Добавьте к списку в самом начале файла DrawingShape.cs следующие директивы using:

using Windows.UI;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Shapes;

using Windows.UI.Xaml.Controls;

Цель этого класса заключается в содержании общего кода для классов Circle и Square. Программа не должна иметь возможности непосредственно создавать экземпляр DrawingShape-объекта.

Измените определение класса DrawingShape, придав ему статус абстрактного (выделено жирным шрифтом):

abstract class DrawingShape

{

}

Добавьте к классу DrawingShape следующие закрытые переменные, показанные жирным шрифтом:

abstract class DrawingShape

{

    protected int size;

    protected int locX = 0, locY = 0;

    protected Shape = null;

}

Поля locX и locY для указания положения объекта на холсте используются как в классе Square, так и в классе Circle, поэтому их можно переместить в абстрактный класс. Также в обоих этих классах используются поля для указания размера объекта при его выводе на холст. Хотя в каждом классе у них разные имена (sideLength и diameter), семантически поля в них выполняют одну и ту же задачу. Хорошим обобщением назначения такого поля станет имя size.

Внутри класс Square использует для вывода фигуры на холст объект типа Rectangle, а класс Circle использует для той же цели Ellipse-объект. Оба этих класса являются частью иерархии, основанной на абстрактном классе Shape, определенном в среде .NET Framework. Для представления обоих этих типов в классе DrawingShape используется поле Shape.

Добавьте к классу DrawingShape следующий конструктор:

abstract class DrawingShape

{

    ...

    public DrawingShape(int size)

    {

        this.size = size;

    }

}

Этот код инициализирует в DrawingShape-объекте поле size.

Добавьте к классу DrawingShape методы SetLocation и SetColor, выделенные в показанном далее коде жирным шрифтом. Эти методы предоставляют реализацию, наследуемую всеми классами, которые являются производными класса DrawingShape. Обратите внимание на то, что они не помечены как виртуальные и в производном классе их перегрузка не ожидается. Кроме того, класс DrawingShape не объявляется реализатором интерфейсов IDraw или IColor (реализацией интерфейсов занимаются Square и Circle, а не этот абстрактный класс), поэтому методы просто объявляются открытыми:

abstract class DrawingShape

{

    ...

    public void SetLocation(int xCoord, int yCoord)

    {

        this.locX = xCoord;

        this.locY = yCoord;

    }

    

    public void SetColor(Color color)

    {

        if (this.shape != null)

        {

            SolidColorBrush brush = new SolidColorBrush(color);

            this.shape.Fill = brush;

        }

    }

}

Добавьте к классу DrawingShape метод Draw. В отличие от предыдущих методов, он объявляется виртуальным, и ожидается, что в любом производном классе для расширения функциональности он будет перегружен. Код метода проверяет поле shape на null-значение, а затем рисует его на холсте. Классы, наследующие этот метод, должны предоставить для создания экземпляра класса в виде shape-объекта свой собственный код. (Не забудьте, что класс Square создает объект Rectangle, а класс Circle — объект Ellipse.)

abstract class DrawingShape

{

    ...

    public virtual void Draw(Canvas canvas)

    {

        if (this.shape == null)

        {

            throw new InvalidOperationException("Shape is null");

        }

 

        this.shape.Height = this.size;

        this.shape.Width = this.size;

        Canvas.SetTop(this.shape, this.locY);

        Canvas.SetLeft(this.shape, this.locX);

        canvas.Children.Add(this.shape);

    }

}

Теперь создание класса DrawingShape завершено. Следующий этап будет заключаться в изменении классов Square и Circle, превращающем их в наследников этого класса, с последующим удалением из них повторяющегося кода.

Изменение классов Square и Circle с превращением их в наследников класса DrawingShape

Выведите в окно редактора класс Square. Измените определение класса Square, указав его в качестве наследника класса DrawingShape в дополнение к указанию того, что в нем реализуются интерфейсы IDraw и IColor:

class Square : DrawingShape, IDraw, IColor

{

    ...

}

Учтите, что класс, наследником которого является класс Square, нужно указать перед указанием о необходимости реализации интерфейсов.

Удалите из класса Square определения полей sideLength, rect, locX и locY. Эти поля не нужны, поскольку теперь они предоставляются классом DrawingShape.

Замените существующий конструктор следующим кодом, вызывающим конструктор, находящийся в базовом классе:

class Square : DrawingShape, IDraw, IColor

{

    public Square(int sideLength)

        : base(sideLength)

    {

    }

    ...

}

Обратите внимание на то, что у этого конструктора пустое тело, поскольку все нужные инициализации выполняются конструктором базового класса.

Удалите из класса Square методы IDraw.SetLocation и IColor.SetColor. Реализацию этих методов предоставляет класс DrawingShape.

Измените определение метода Draw. Объявите его с ключевыми словами public override, а также удалите ссылку на интерфейс IDraw. Базовые действия для этого метода также уже предоставляются классом DrawingShape, но его функцио­нальность будет расширена специальным кодом, требующимся классу Square:

public override void Draw(Canvas canvas)

{

    ...

}

Замените тело метода Draw кодом, выделенным жирным шрифтом:

public override void Draw(Canvas canvas)

{

    if (this.shape != null)

    {

        canvas.Children.Remove(this.shape);

    }

    else

    {

        this.shape = new Rectangle();

    }

 

    base.Draw(canvas);

}

Эти инструкции создают экземпляр поля shape, унаследованного от класса DrawingShape, в качестве нового экземпляра класса Rectangle — при условии, что такой экземпляр еще не был создан. Затем они вызывают в классе DrawingShape метод Draw.

Повторите все предыдущие операции в отношении класса Circle, применив при этом для конструктора имя Circle и параметр diameter и создав в методе Draw экземпляр поля shape в качестве нового Ellipse-объекта. Готовый код для класса Circle должен приобрести следующий вид:

class Circle : DrawingShape, IDraw, IColor

{

    public Circle(int diameter)

        : base(diameter)

    {

    }

 

    public override void Draw(Canvas canvas)

    {

        if (this.shape != null)

        {

            canvas.Children.Remove(this.shape);

        }

        else

        {

            this.shape = new Ellipse();

        }

 

        base.Draw(canvas);

    }

}

Щелкните в меню Отладка на пункте Начать отладку. Когда откроется окно Drawing Pad, убедитесь в том, что при щелчке левой кнопкой мыши на холсте появляются Square-объекты (квадраты), а при щелчке правой кнопкой мыши — Circle-объекты (круги). Приложение должно вести себя точно так же, как и прежде.

Вернитесь в среду Visual Studio и остановите отладку.

Еще раз о совместимости с Windows Runtime

В главе 9 «Создание типов значений с использованием перечислений и структур» приводилось описание того, как платформа Windows из Windows 8 далее реализует модель Windows Runtime (WinRT) в качестве надстройки над исходными API-интерфейсами Windows, предоставляя разработчикам упрощенный интерфейс программирования для создания неуправляемых приложений. (Под неуправляемыми понимаются такие приложения, которые не запускаются с использованием .NET Framework; они создаются с использованием языка C++, а не C#.) Управляемые приложения для запуска приложений .NET Framework используют общеязыковую среду выполнения (common language runtime (CLR)). Платформа .NET Framework предоставляет обширный набор библиотек и функций. Под управлением Windows 7 и более ранних версий операционной системы CLR реализует эти функции путем использования исходных API-интерфейсов Windows. При создании приложений для настольных систем или корпоративных приложений и служб, запускаемых под Windows 10, этот же набор функций по-прежнему доступен (хотя сама платформа обновлена до версии 4.6), и любые C#-приложения, работающие под Windows 7, должны без всяких изменений запускаться и под Windows 10.

Под управлением Windows 10 UWP-приложения всегда запускаются с использованием WinRT. Это означает, что при создании UWP-приложений, использующих управляемые языки, подобные C#, CLR фактически вызывает WinRT, а не исходные API-интерфейсы Windows. Microsoft предоставила отображаемый уровень между CLR и WinRT, который способен явным образом переводить запросы на создание объектов и вызывать методы, созданные под .NET Framework, в эквивалентные запросы на создание объектов и вызовы методов в WinRT. Например, когда создается значение Int32 для .NET Framework (int в C#), этот код переводится в код создания значения, использующего эквивалентный тип данных WinRT. Но хотя в CLR и в WinRT имеется большой объем перекрывающихся функциональных возможностей, соответствующие функции в WinRT есть не для всех функций .NET Framework 4.6. Поэтому у UWP-приложений есть доступ лишь к сокращенному набору типов и методов, предоставляемому .NET Framework 4.6. (Механизм IntelliSense в Visual Studio 2015 автоматически показывает ограниченное представление доступных функций при использовании C# для создания UWP-приложений, опуская те типы и методы, которые недоступны при использовании WinRT.)

В то же время WinRT предоставляет внушительный набор функций и типов, не имеющих прямого эквивалента в .NET Framework или работающих совершенно иначе, нежели соответствующие им функции в .NET Framework, и не позволяющих легко выполнять их перевод. WinRT открывает доступ к этим функциям для CLR путем использования уровня отображения, делающего их похожими на типы и методы, имеющиеся в .NET Framework, и их можно вызвать из управляемого кода напрямую.

Интеграция, реализованная в CLR и в WinRT, позволяет CLR свободно пользоваться типами, имеющимися в WinRT, но при этом она также поддерживает функциональную взаимозаменяемость в обратном направлении: вы можете определять типы, используя управляемый код, и открывать к ним доступ со стороны неуправляемых приложений при условии, что эти типы соответствуют ожиданиям WinRT. В главе 9 в этом отношении были обозначены требования структур (методы экземпляров и статические методы в структурах через WinRT недоступны, а закрытые поля не поддерживаются). Если классы создаются с прицелом на использование через WinRT в неуправляемых приложениях, то нужно придерживаться следующих правил.

• Все открытые поля, параметры и возвращаемые значения всех открытых методов должны относиться к типам WinRT или типам .NET Framework, свободно переводимым средой WinRT в типы WinRT. Примеры поддерживаемых типов .NET Framework включают согласующиеся типы значений (например, структуры и перечисления) и те типы значений, которые соответствуют простым типам C# (int, long, float, double, string и т.д.). Закрытые поля поддерживаются в классах и могут быть любого типа, доступного в .NET Framework, они не должны согласовываться с WinRT.

• Классы не могут перегружать методы System.Object, за исключением метода ToString, и в них не могут объявляться защищенные конструкторы.

• У пространства имен, в котором определяется класс, должно быть такое же имя, как и у сборки, реализующей класс. Кроме того, имя пространства имен (а следовательно, и имя сборки) не должно начинаться с «Windows».

• Задать через WinRT наследование от управляемых типов в неуправляемых приложениях невозможно. Поэтому все открытые классы должны быть запечатанными. Если нужно реализовать полиморфизм, можно создать открытый интерфейс и реализовать этот интерфейс в классах, в которых должен проявляться полиморфизм.

• Можно выдавать любой тип исключений, включенный в поднабор .NET Framework, доступный UWP-приложениям, — создавать собственные классы исключений вы не сможете. Если при вызове из неуправляемого приложения ваш код выдает необрабатываемое исключение, WinRT выдает эквивалентное исключение в неуправляемом коде.

Далее в книге будут рассматриваться и другие требования, налагаемые WinRT на функциональные возможности кода C#. Эти требования будут выделяться при рассмотрении каждой функции.

Выводы

В этой главе вы увидели, как определяются и реализуются интерфейсы и абстрактные классы. Различные допустимые (Да) и недопустимые (Нет) комбинации ключевых слов при определении методов для интерфейсов, классов и структур сведены в табл. 13.1.

Таблица 13.1

Ключевое слово

Интерфейс

Абстрактный класс

Класс

Запечатанный класс

Структура

abstract

Нет

Да

Нет

Нет

Нет

new

Да*

Да

Да

Да

Нет**

override

Нет

Да

Да

Да

Нет***

private

Нет

Да

Да

Да

Да

protected

Нет

Да

Да

Да

Нет****

public

Нет

Да

Да

Да

Да

sealed

Нет

Да

Да

Да

Нет

virtual

Нет

Да

Да

Нет

Нет

* Интерфейс может расширять другой интерфейс и вводить новый метод с точно такой же сигнатурой.

** Структуры не поддерживают наследование, поэтому они не могут скрывать методы.

*** Структуры не поддерживают наследование, поэтому они не могут перегружать методы.

**** Структуры не поддерживают наследование, они подразумеваются запечатанными и не могут быть производными от других структур.

Если хотите продолжить работу и изучить следующую главу, оставьте открытой среду Visual Studio 2015 и переходите к главе 14.

Если хотите выйти из среды Visual Studio 2015, то в меню Файл щелкните на пункте Выход. Если увидите диалоговое окно с предложением сохранить изменения, щелкните на кнопке Да и сохраните проект.

Краткий справочник

Чтобы

Сделайте следующее

Объявить интерфейс

Воспользуйтесь ключевым словом interface, например:

interface IDemo

{

    string GetName();

    string GetDescription();

}

Реализовать интерфейс

Объявите класс, воспользовавшись таким же синтаксисом, как и при объявлении класса-наследника, а затем реализуйте все функции, входящие в интерфейс, например:

class Test : IDemo

{

    public string IDemo.GetName()

    {

    ...

    }

    public string IDemo.GetDescription()

    {

    ...

    }

}

Создать абстрактный класс, который можно использовать только в качестве базового класса, содержащего абстрактные методы

Объявите класс, воспользовавшись ключевым словом abstract. Укажите при объявлении каждого абстрактного метода ключевое слово abstract, не указывая при этом тело метода, например:

abstract class GrazingMammal

{

    abstract void DigestGrass();

    ...

}

Создать запечатанный класс, который не может использоваться в качестве базового класса

Объявите класс, воспользовавшись ключевым словом sealed, например:

sealed class Horse

{

    ...

}

 

Назад: 12. Работа с наследованием
Дальше: 14. Использование сборщика мусора и управление ресурсами

Антон
Перезвоните мне пожалуйста 8(812)642-29-99 Антон.
Антон
Перезвоните мне пожалуйста по номеру 8(904) 332-62-08 Антон.
Антон
Перезвоните мне пожалуйста, 8 (904) 606-17-42 Антон.
Антон
Перезвоните мне пожалуйста по номеру. 8 (953) 367-35-45 Антон
Ксения
Текст от профессионального копирайтера. Готово через 1 день. Консультация бесплатно. Жми roholeva(точка)com