Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 36 (всего у книги 58 страниц)
Далее создадим классы, наследующие класс PhoneNumber: Friend и Supplier. Эти классы приведены ниже. // Класс для телефонных номеров друзей. class Friend : PhoneNumber { public Friend(string n, string num, bool wk) : base(n, num) { IsWorkNumber = wk; } public bool IsWorkNumber { get; private set; } // ... } // Класс для телефонных номеров поставщиков. class Supplier : PhoneNumber { public Supplier(string n, string num) : base(n, num) { } // ... }
Обратите внимание на то, что в класс Friend введено свойство IsWorkNumber, воз вращающее логическое значение true, если номер телефона является рабочим.
Для управления списками телефонных номеров создадим еще один класс под на званием PhoneList. Его следует сделать обобщенным, поскольку он должен служить для управления любым списком телефонных номеров. В функции такого управления должен, в частности, входить поиск телефонных номеров по заданным именам и нао борот, поэтому на данный класс необходимо наложить ограничение по типу, требую щее, чтобы объекты, сохраняемые в списке, были экземплярами класса, производного от класса PhoneNumber. // Класс PfconeList способен управлять любым видом списка телефонных // номеров, при условии, что он является производным от класса PhoneNumber. class PhoneList Ограничение на базовый класс разрешает коду в классе PhoneList доступ к свой ствам Name и Number для управления любым видом списка телефонных номеров. Оно гарантирует также, что для построения объекта класса PhoneList будут использовать ся только доступные типы. Обратите внимание на то, что в классе PhoneList генери руется исключение NotFoundException, если имя или номер телефона не найдены. Это специальное исключение, объявляемое ниже. class NotFoundException : Exception { /* Реализовать все конструкторы класса Exception. Эти конструкторы выполняют вызов конструктора базового класса. Класс NotFoundException ничем не дополняет класс Exception и поэтому не требует никаких дополнительных действий. */ public NotFoundException() : base() { } public NotFoundException(string str) : base(str) { } public NotFoundException( string str, Exception inner) : base(str, inner) { } protected NotFoundException( System.Runtime.Serialization.Serializationlnfo si, System.Runtime.Serialization.StreamingContext sc) : base(si, sc) { } } В данном примере используется только конструктор, вызываемый по умолчанию, но ради наглядности этого примера в классе исключения NotFoundException реали зуются все конструкторы, определенные в классе Exception. Обратите внимание на то, что эти конструкторы вызывают эквивалентный конструктор базового класса, опре деленный в классе Exception. А поскольку класс исключения NotFoundException ничем не дополняет базовый класс Exception, то для любых дополнительных дей ствий нет никаких оснований. В приведенной ниже программе все рассмотренные выше фрагменты кода объе диняются вместе, а затем демонстрируется применение класса PhoneList. Кро ме того, в ней создается класс EmailFriend. Этот класс не наследует от класса PhoneNumber, а следовательно, он не может использоваться для создания объектов класса PhoneList. // Более практический пример, демонстрирующий применение // ограничения на базовый класс. using System; // Специальное исключение, генерируемое в том случае, // если имя или номер телефона не найдены. class NotFoundException : Exception { /* Реализовать все конструкторы класса Exception. Эти конструкторы выполняют вызов конструктора базового класса. Класс NotFoundException ничем не дополняет класс Exception и поэтому не требует никаких дополнительных действий. */ public NotFoundException() : base() { } public NotFoundException(string str) : base(str) { } public NotFoundException( string str, Exception inner) : base(str, inner) { } protected NotFoundException) System.Runtime.Serialization.SerializationInfo si, System.Runtime.Serialization.StreamingContext sc) : base(si, sc) { } } // Базовый класс, в котором хранятся имя абонента и номер его телефона. class PhoneNumber { public PhoneNumber(string n, string num) { Name = n; Number = num; } public string Number { get; set; } public string Name { get; set; } } // Класс для телефонных номеров друзей. class Friend : PhoneNumber { public Friend(string n, string num, bool wk) : base(n, num) { IsWorkNumber = wk; } public bool IsWorkNumber { get; private set; } // ... } // Класс для телефонных номеров поставщиков. class Supplier : PhoneNumber { public Supplier(string n, string num) : base (n, num) { } // ... } // Этот класс не наследует от класса PhoneNumber. class EmailFriend { // ... } // Класс PhoneList способен управлять любым видом списка телефонных номеров. // при условии, что он является производным от класса PhoneNumber. class PhoneList Ниже приведен результат выполнения этой программы. Гари: 555-6756 (рабочий) Компания NetworkCity: 555-2564 Поэкспериментируйте с этой программой. В частности, попробуйте составить раз ные виды списков телефонных номеров или воспользоваться свойством IsWorkNumber в классе PhoneList. Вы сразу же обнаружите, что компилятор не позволит вам этого сделать, потому что свойство IsWorkNumber определено в классе Friend, а не в классе PhoneNumber, а следовательно, оно неизвестно в классе PhoneList. Применение ограничения на интерфейс Ограничение на интерфейс позволяет указывать интерфейс, который должен быть реализован аргументом типа. Это ограничение служит тем же основным целям, что и ограничение на базовый класс. Во-первых, оно позволяет использовать члены интер фейса в обобщенном классе. И во-вторых, оно гарантирует использование только тех аргументов типа, которые реализуют указанный интерфейс. Это означает, что для лю бого ограничения, накладываемого на интерфейс, аргумент типа должен обозначать сам интерфейс или же тип, реализующий этот интерфейс. Ниже приведена общая форма наложения ограничения на интерфейс, в которой используется оператор where: where Т : имя_интерфейса где Т – это имя параметра типа, а имя_интерфейса – конкретное имя ограничивае мого интерфейса. В этой форме ограничения может быть указан список интерфейсов через запятую. Если ограничение накладывается одновременно на базовый класс и ин терфейс, то первым в списке должен быть указан базовый класс. Ниже приведена программа, демонстрирующая наложение ограничения на ин терфейс и представляющая собой переработанный вариант предыдущего примера программы, управляющей списками телефонных номеров. В этом варианте класс PhoneNumber преобразован в интерфейс IPhoneNumber, который реализуется в клас сах Friend и Supplier. // Применить ограничение на интерфейс. using System; // Специальное исключение, генерируемое в том случае, // если имя или номер телефона не найдены. class NotFoundException : Exception { /* Реализовать все конструкторы класса Exception. Эти конструкторы выполняют вызов конструктора базового класса. Класс NotFoundException ничем не дополняет класс Exception и поэтому не требует никаких дополнительных действий. */ public NotFoundException() : base() { } public NotFoundException(string str) : base(str) { } public NotFoundException( string str,Exception inner) : base(str, inner) { } protected NotFoundException( System.Runtime.Serialization.SerializationInfо si, System.Runtime.Serialization.StreamingContext sc) : base(si, sc) { } } // Интерфейс, поддерживающий имя и номер телефона. public interface IPhoneNumber { string Number { get; set; } string Name { get; set; } } // Класс для телефонных номеров друзей. // В нем реализуется интерфейс IPhoneNumber. class Friend : IPhoneNumber { public Friend(string n, string num, bool wk) { Name = n; Number = num; IsWorkNumber = wk; } public bool IsWorkNumber { get; private set; } // Реализовать интерфейс IPhoneNumber. public string Number { get; set; } public string Name { get; set; } // ... } // Класс для телефонных номеров поставщиков. class Supplier : IPhoneNumber { public Supplier(string n, string num) { Name = n; Number = num; } // Реализовать интерфейс IPhoneNumber. public string Number { get; set; } public string Name { get; set; } // ... } // В этом классе интерфейс IPhoneNumber не реализуется. class EmailFriend { // ... } // Класс PhoneList способен управлять любым видом списка телефонных // номеров, при условии, что он реализует интерфейс PhoneNumber. class PhoneList В этой версии программы ограничение на интерфейс, указываемое в классе PhoneList, требует, чтобы аргумент типа реализовал интерфейс IPhoneList. А по скольку этот интерфейс реализуется в обоих классах, Friend и Supplier, то они от носятся к допустимым типам, привязываемым к типу Т. В то же время интерфейс не реализуется в классе EmailFriend, и поэтому этот класс не может быть привязан к типу Т. Для того чтобы убедиться в этом, удалите символы комментария в двух по следних строках кода в методе Main(). Вы сразу же обнаружите, что программа не компилируется. Применение ограничения new() на конструктор Ограничение new() на конструктор позволяет получать экземпляр объекта обоб щенного типа. Как правило, создать экземпляр параметра обобщенного типа не уда ется. Но это положение изменяет ограничение new(), поскольку оно требует, чтобы аргумент типа предоставил конструктор без параметров. Им может быть конструктор, вызываемый по умолчанию и предоставляемый автоматически, если явно определяе мый конструктор отсутствует или же конструктор без параметров явно объявлен поль зователем. Накладывая ограничение new(), можно вызывать конструктор без параме тров для создания объекта. Ниже приведен простой пример, демонстрирующий наложение ограничения new(). // Продемонстрировать наложение ограничения new() на конструктор. using System; class MyClass { public MyClass() { // ... } // ... } class Test Прежде всего обратите внимание на объявление класса Test. class Test В силу накладываемого ограничения new() любой аргумент типа должен предо ставлять конструктор без параметров. Далее проанализируем приведенный ниже конструктор класса Test. public Test() { // Этот код работоспособен благодаря наложению ограничения new(). obj = new Т(); // создать объект типа Т } В этом фрагменте кода создается объект типа Т, и ссылка на него присваивается переменной экземпляра obj. Такой код допустим только потому, что ограничение new() требует наличия конструктора. Для того чтобы убедиться в этом, попробуйте сначала удалить ограничение new(), а затем попытайтесь перекомпилировать про грамму. В итоге вы получите сообщение об ошибке во время компиляции. В методе Main() получается экземпляр объекта типа Test, как показано ниже. Test Обратите внимание на то, что аргументом типа в данном случае является класс MyClass и что в этом классе определяется конструктор без параметров. Следователь но, этот класс допускается использовать в качестве аргумента типа для класса Test. Следует особо подчеркнуть, что в классе MyClass совсем не обязательно определять конструктор без параметров явным образом. Его используемый по умолчанию кон структор вполне удовлетворяет накладываемому ограничению. Но если классу по требуются другие конструкторы, помимо конструктора без параметров, то придется объявить явным образом и вариант без параметров. Что касается применения ограничения new(), то следует обратить внимание на три других важных момента. Во-первых, его можно использовать вместе с другими ограничениями, но последним по порядку. Во-вторых, ограничение new() позволяет конструировать объект, используя только конструктор без параметров, – даже если доступны другие конструкторы. Иными словами, передавать аргументы конструктору параметра типа не разрешается. И в-третьих, ограничение new() нельзя использовать одновременно с ограничением типа значения, рассматриваемым далее. Ограничения ссылочного типа и типа значения Два других ограничения позволяют указать на то, что аргумент, обозначающий тип, должен быть либо ссылочного типа, либо типа значения. Эти ограничения оказывают ся полезными в тех случаях, когда для обобщенного кода важно провести различие между ссылочным типом и типом значения. Ниже приведена общая форма ограниче ния ссылочного типа. where Т : class В этой форме с оператором where ключевое слово class указывает на то, что ар гумент Т должен быть ссылочного типа. Следовательно, всякая попытка использовать тип значения, например int или bool, вместо Т приведет к ошибке во время компи ляции. Ниже приведена общая форма ограничения типа значения. where Т : struct В этой форме ключевое слово struct указывает на то, что аргумент Т должен быть типа значения. (Напомним, что структуры относятся к типам значений.) Следователь но, всякая попытка использовать ссылочный тип, например string, вместо T приведет к ошибке во время компиляции. Но если имеются дополнительные ограничения, то в любом случае class или struct должно быть первым по порядку накладываемым ограничением. Ниже приведен пример, демонстрирующий наложение ограничения ссылочного типа. // Продемонстрировать наложение ограничения ссылочного типа. using System; class MyClass { // ... } // Наложить ограничение ссылочного типа. class Test Обратите внимание на следующее объявление класса Test. class Test Ограничение class требует, чтобы любой аргумент Т был ссылочного типа. В дан ном примере кода это необходимо для правильного выполнения операции присваи вания в конструкторе класса Test. public Test() { // Следующий оператор допустим только потому, что // аргумент Т гарантированно относится к ссылочному // типу, что позволяет присваивать пустое значение. obj = null; } В этом фрагменте кода переменной obj типа T присваивается пустое значение. Та кое присваивание допустимо только для ссылочных типов. Как правило, пустое зна чение нельзя присвоить переменной типа значения. (Исключением из этого правила является обнуляемый тип, который представляет собой специальный тип структуры, инкапсулирующий тип значения и допускающий пустое значение (null). Подробнее об этом – в главе 20.) Следовательно, в отсутствие ограничения такое присваивание было бы недопустимым, и код не подлежал бы компиляции. Это один из тех случаев, когда для обобщенного кода может оказаться очень важным различие между типами значений и ссылочными типами. Ограничение типа значения является дополнением ограничения ссылочного типа. Оно просто гарантирует, что любой аргумент, обозначающий тип, должен быть типа значения, в том числе struct и enum. (В данном случае обнуляемый тип не относится к типу значения.) Ниже приведен пример наложения ограничения типа значения. // Продемонстрировать наложение ограничения типа значения. using System; struct MyStruct { // ... } class MyClass { // ... } class Test В этом примере кода класс Test объявляется следующим образом. class Test На параметр типа Т в классе Test накладывается ограничение struct, и поэто му к нему могут быть привязаны только аргументы типа значения. Это означает, что объявления Test и Test вполне допустимы, тогда как объявление Test недопустимо. Для того чтобы убедиться в этом, удалите символы ком ментария в начале последней строки приведенного выше кода и перекомпилируйте его. В итоге вы получите сообщение об ошибке во время компиляции. Установление связи между двумя параметрами типа с помощью ограничения Существует разновидность ограничения на базовый класс, позволяющая установить связь между двумя параметрами типа. В качестве примера рассмотрим следующее объявление обобщенного класса. class Gen В этом объявлении оператор where уведомляет компилятор о том, что аргумент типа, привязанный к параметру типа V, должен быть таким же, как и аргумент типа, привязанный к параметру типа Т, или же наследовать от него. Если подобная связь отсутствует при объявлении объекта типа Gen, то во время компиляции возникнет ошибка. Такое ограничение на параметр типа называется неприкрытым ограничением типа. В приведенном ниже примере демонстрируется наложение этого ограничения. // Установить связь между двумя параметрами типа. using System; class А { // ... } class В : А { // ... } // Здесь параметр типа V должен наследовать от параметра типа Т. class Gen Обратите внимание на то, что класс В наследует от класса А. Проанализируем далее оба объявления объектов класса Gen в методе Main(). Как следует из комментария к первому объявлению Gen х = new Gen(); оно вполне допустимо, поскольку класс В наследует от класса А. Но второе объявление // Gen у = new Gen(); недопустимо, поскольку класс А не наследует от класса В. Применение нескольких ограничений С параметром типа может быть связано несколько ограничений. В этом случае ограничения указываются списком через запятую. В этом списке первым должно быть указано ограничение class либо struct, если оно присутствует, или же ограничение на базовый класс, если оно накладывается. Указывать ограничения class или struct одновременно с ограничением на базовый класс не разрешается. Далее по списку должно следовать ограничение на интерфейс, а последним по порядку – ограничение new(). Например, следующее объявление считается вполне допустимым. class Gen В данном случае параметр типа Т должен быть заменен аргументом типа, наследу ющим от класса MyClass, реализующим интерфейс IMyInterface и использующим конструктор без параметра. Если же в обобщении используются два или более параметра типа, то ограничения на каждый из них накладываются с помощью отдельного оператора where, как в при веденном ниже примере. // Использовать несколько операторов where. using System; // У класса Gen имеются два параметра типа, и на оба накладываются // ограничения с помощью отдельных операторов where. class Gen В данном примере класс Gen принимает два аргумента с ограничениями, накла дываемыми с помощью отдельных операторов where. Обратите особое внимание на объявление этого класса. class Gen Как видите, один оператор where отделяется от другого только пробелом. Другие знаки препинания между ними не нужны и даже недопустимы. Получение значения, присваиваемого параметру типа по умолчанию Как упоминалось выше, при написании обобщенного кода иногда важно провести различие между типами значений и ссылочными типами. Такая потребность возника ет, в частности, в том случае, если переменной параметра типа должно быть присвое но значение по умолчанию. Для ссылочных типов значением по умолчанию является null, для неструктурных типов значений – 0 или логическое значение false, если это тип bool, а для структур типа struct – объект соответствующей структуры с полями, установленными по умолчанию. В этой связи возникает вопрос: какое значение следует присваивать по умолчанию переменной параметра типа: null, 0 или нечто другое? Например, если в следующем объявлении класса Test: class Test переменной obj требуется присвоить значение по умолчанию, то какой из двух вариантов obj = null; // подходит только для ссылочных типов или obj = 0; // подходит только для числовых типов и // перечислений, но не для структур следует выбрать? Для разрешения этой дилеммы можно воспользоваться еще одной формой оператора default, приведенной ниже. default(тип) Эта форма оператора default пригодна для всех аргументов типа, будь то типы значений или ссылочные типы. Ниже приведен короткий пример, демонстрирующий данную форму оператора default. // Продемонстрировать форму оператора default. using System; class MyClass { // ... } // Получить значение, присваиваемое параметру типа Т по умолчанию. class Test Вот к какому результату приводит выполнение этого кода. Переменная x.obj имеет пустое значение В C# разрешается создавать обобщенные структуры. Синтаксис для них такой же, как и для обобщенных классов. В качестве примера ниже приведена программа, в ко торой создается обобщенная структура XY для хранения координат X, Y. // Продемонстрировать применение обобщенной структуры. using System; // Эта структура является обобщенной. struct XY При выполнении этой программы получается следующий результат. 10, 20 88, 99 Как и на обобщенные классы, на обобщенные структуры могут накладываться огра ничения. Например, на аргументы типа в приведенном ниже варианте структуры XY накладывается ограничение типа значения. struct XY Как следует из приведенных выше примеров, в методах, объявляемых в обобщен ных классах, может использоваться параметр типа из данного класса, а следовательно, такие методы автоматически становятся обобщенными по отношению к параметру типа. Но помимо этого имеется возможность объявить обобщенный метод со своими собственными параметрами типа и даже создать обобщенный метод, заключенный в необобщенном классе. Рассмотрим для начала простой пример. В приведенной ниже программе объяв ляется необобщенный класс ArrayUtils, а в нем – статический обобщенный метод CopyInsert(). Этот метод копирует содержимое одного массива в другой, вводя по ходу дела новый элемент в указанном месте. Метод CopyInsert() можно использо вать вместе с массивами любого типа. // Продемонстрировать применение обобщенного метода. using System; // Класс обработки массивов. Этот класс не является обобщенным. class ArrayUtils { // Копировать массив, вводя по ходу дела новый элемент. // Этот метод является обобщенным. public static bool CopyInsert