Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky

.pdf
Скачиваний:
32
Добавлен:
22.03.2016
Размер:
2.08 Mб
Скачать

double (System.Double) реализуют интерфейс IComparable. При других типизирующих аргументах на этапе исполнения программы будет генерироваться исключение InvalidCastException. Самое главное и плохое допущенная ошибка в использовании неверного типизирующего аргумента не будет распознаваться на этапе компиляции.

Компилятор сможет идентифицировать неверное задание типизирующих аргументов только в том случае, если в декларацию обобщённого класса включить так называемый список ограничений (list of constaints) типизирующих параметров

"типизирующие параметры:список ограничений" .

Элемент этого списка имеет вид:

where имя_типизирующего_параметра:список_ограничений

Здесь where служебное слово "служебное слово:where" языка C#, за ним следует имя того типизирующего параметра обобщённого класса, для которого вводятся ограничения. Список допустимых ограничений представляет собой список обозначений типов, каждый из которых может использоваться в качестве типизирующего аргумента, соответствующего данному параметру.

В нашем примере со стеком, помещать в который можно только значения, тип которых допускает применение метода CompareTo(), обобщённый класс можно

определить так:

public class Stack <ItemType> where ItemType:IComparable

{private ItemType [ ] item=new ItemType[100]; public void Push(ItemType data) {

if (data.CompareTo(default(ItemType))<0) return;

}

public ItemType Pop( ) { …}

}

На основе такой декларации обобщённого класса компилятор будет проверять все специализации и сообщать об ошибке в каждом случае, когда типизирующий аргумент не является классом, реализовавшим интерфейс IСomparable. Обратите внимание, что теперь в методе Push() нет приведения типа параметра к типу

интерфейса IComparable.

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

IComparable.

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

В то же время для одного параметра в одном операторе where можно указать

несколько видов ограничений.

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

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

Определены пять видов ограничений:

where T: struct типизирующий аргумент должен быть типом значений.

where T: class типизирующий аргумент должен быть типом ссылок. Ссылки могут относиться к любым классам, интерфейсам, делегатам и типам массивов.

where T: new() – типизирующий аргумент должен иметь конструктор без параметров. Когда для одного типизирующего параметра используется несколько ограничений, ограничение new() в списке должно быть последним.

where T: имя_класса типизирующий аргумент должен быть либо указанным классом, либо классом, производным от него.

where T:U, где в этой конструкции U – типизирующий параметр обобщения. При таком ограничении подставляемый вместо параметра T типизирующий аргумент должен быть либо тем же аргументом, который заменяет параметр

U, либо должен быть именем производного от него класса. Этот вид

ограничения называют ограничением с помощью явного типа (naked type

constraint).

Список ограничений типизирующих параметров позволяет компилятору

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

Типизирующие параметры, для которых ограничения не указаны, называют

свободными (unbounded) типизирующими параметрами. "типизирующие

параметры:свободные" Для таких параметров требуется соблюдение следующих требований:

Операции != и == нельзя использовать, так как нет уверенности, что конкретные

типизирующие аргументы будут поддерживать эти операции.

Они могут быть конвертированы к типу System.Object или преобразованы из

этого типа.

Они могут явно приводиться к любому интерфейсному типу.

Их можно сравнивать со значением null. Если свободный типизирующий

параметр сравнивается со значением null, то результат сравнения всегда false, когда типизирующий аргумент является типом значений.

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

"типизирующие параметры:свободные " , и необходимо, чтобы этот параметр

соответствовал типизирующему параметру обобщённого класса. Пример из MSDN: class List <T> {

void Add<U> (List< U > item) where U:T { ...}

}

В данном примере обобщённый класс List <T> включает в качестве члена обобщённый метод Add<U>. (Обобщённым методам посвящён далее раздел 18.6).

Для типизирующего параметра U ограничение задано с помощью указания явного типа T. Таким образом, T – ограничение (заданное указанием явного типа) в

обобщённом методе Add(). В то же время T в контексте объявления обобщённого класса List <T> является свободным типизирующим параметром.

Ограничение с помощью указания явного типа может быть непосредственно

использоваться в объявлении обобщённого класса. Пример (из MSDN): class SampleClass<T,U,V> where T:V { }

В данном примере требуется, чтобы аргумент, заменяющий параметр Т, был

типом, заменяющим параметр V, либо должен быть производным от него типом.

Полезность ограничений явным типом в объявлениях обобщённых классов

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

Перечисленные пять видов ограничений неравнозначны. Первичными

ограничениями являются: имя_класса, class и struct.

 

Вторичные ограничения:

имя_интерфейса ,

типизирующий_параметр

(ограничение с помощью указания явного типа).

 

Ограничения третьего уровня: new().

 

Именно в таком порядке (первое, второе, третье) ограничения могут входить в

оператор where для одного конкретного типизирующего параметра. При этом

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

может быть любым и ограничение третьего уровня, т.е.

new(), может быть только

одно. Ограничения отделяются друг от друга в операторе

where запятыми.

Ограничения одного параметра параметров (отдельные операторы where) никак не разделяются (обычно их разделение обозначается пробельными символами).

При ограничении в виде имени класса: этот класс не должен быть запечатан (sealed);

он не может иметь тип: Array, Delegate, Enum, Value Type;

он не может иметь тип оbject.

Примеры объявлений с ограничениями типизирующих параметров:

Class MyClass < T > where T: class … Class newClass <T, F> where T: Stream where F:IComparable <int>, new() …

18.4. Обобщённые структуры "обобщённые структуры"

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

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

(хотя эти правила общие и для классов и для структур).

Начнем с того, что обобщения и классов и структур допускают перегрузку.

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

перегрузки обобщенных структур: struct Element<D> {

public D memb;

}

struct Element<E, R> { public E memb1; public R memb2;

}

В данном случае два открытых типа (два обобщенных структурных типа)

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

пространстве имен, например, обобщенную структуру с заголовком

struct

Element<T, F>.

 

 

Обобщенный тип называют открытым типом

"тип:открытый" , а

сконструированный на его основе полностью специфицированный тип закрытым

"тип:закрытый" , подчеркивая, что на основе этого сконструированного типа уже нельзя объявлять другие типы.

Пример переменной и экземпляра структуры закрытого типа:

Element<int> ded = new Element<int>();

Console.WriteLine("ded.memb = " + ded.memb);

Результат выполнения: ded.memb = 0

Как всегда по умолчанию члены структур инициализируются умалчиваемыми

значениями соответствующих типов. В нашем примере целочисленное поле структуры равно нулю.

Следует отметить, что обобщенный тип и одноименный с ним регулярный тип считаются разными типами. Например, наряду с обобщенным классом Example<U>

в той же программе можно декларировать такой регулярный класс

"регулярный

класс" : public class Example { }

 

 

Это не приведет к ошибочной ситуации.

 

Обобщенные типы могут включать

статические члены . В этих случаях

каждый статический член обобщенного типа "статический член:обобщённого типа"

при создании сконструированных типов «размножается». То есть каждому закрытому типу принадлежит свой экземпляр статического члена обобщенного типа. Пример обобщённого типа структур со статическим членом (программа

18_02.cs): using System;

struct memb<T> { public static int count; public T data;

public memb (T d){ data = d; count++;

}

}

class Program { static void Main( ) {

memb<char> mc1 = new memb<char>('Z'); memb<char> mc2 = new memb<char>('F'); memb<byte> mb = new memb<byte>(12);

Console.WriteLine("memb<char>.count = " + memb<char>.count); Console.WriteLine("memb<byte>.count = " + memb<byte>.count);

}

}

}

Результаты выполнения программы: memb<char>.count = 2 memb<byte>.count = 1

В обобщенный тип структур memb<T> входит счетчик экземпляров public

static int count; По умолчанию он инициализируется нулевым значением. При

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

В основной программе созданы два объекта закрытой структуры memb< char> и

один экземпляр типа memb< byte>. Результаты выполнения программы

иллюстрируют правило «размножения» статических членов обобщенных типов.

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

(он в свою очередь может быть параметризован типизирующим параметром, но это не показано в приведенном примере).

В качестве примера обобщенного структурного типа со статическими

методами рассмотрим такую программу (18_03.cs): using System;

struct GenStr<T,V>{

static GenStr( ) { //статический конструктор

Console.Write("{0}+{1}", typeof(T).Name, typeof(V).Name);

}

public static void method( ) { } //статический метод

}

class Program { static void Main( ) {

GenStr<string, int>.method( );

}

}

В обобщенном типе GenStr<T,V> объявлены статический конструктор и статический метод method(), не имеющий параметров и «ничего не делающий». В

основной программе конструируется закрытый структурный тип GenStr<string, int>

и для него выполняется обращение к статическому методу method(). Тем самым неявно вызывается статический конструктор, в теле которого выводятся названия типов, использованных в качестве типизирующих аргументов при объявлении

закрытого структурного типа. Результаты выполнения программы:

String+Int32

При вложении обобщенных типов "обобщённый тип:вложение "

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

обобщённая структура: struct memb<T> {

struct into<T> { T field;

}

}

Объявление корректно, но при создании закрытого типа, например, memb<int>, вложенная структура продолжает иметь открытый тип into<T>, и

конкретный тип поля field, остается, по крайней мере, непонятным. В следующем

примере показано корректное объявление вложенных обобщенных структур: struct memb<T> {

struct into<U> {

Tfield1;

Ufield2;

}

}

Типизирующий параметр Т внешнего обобщенного типа доступен и в нем и во

вложенном типе. В то же время параметр U доступен только во вложенном обобщенном типе. При таком объявлении закрытый тип memb<int> определяет тип поля field1, но оставляет обобщенной структуру into<U> и не влияет на тип поля

field2.

18.5. Обобщённые интерфейсы "обобщённый интерфейс"

Напомним, что членами интерфейса могут быть прототипы методов, свойств,

индексаторов, событий. В обобщенном интерфейсе типизирующие параметры определяют типы возвращаемых значений и параметров членов интерфейса. Как и для классов и структур признаком обобщенного интерфейса является список типизирующих параметров в его объявлении. Типизирующие параметры это идентификаторы, разделенные запятыми и помещенные в угловые скобки. Пример:

public interface IExample<T> {

// обобщенный интерфейс

char this[T index]{get; set;}

// прототип индексатора

int add(T x, T y);

// прототип метода

}

 

В обобщенном интерфейсе IExample<T> один типизирующий параметр. Он специфицирует тип параметра прототипа индексатора, а для прототипа метода определяет типы параметров.

Обобщенный интерфейс может быть реализован как обобщенным, так и регулярным типом. Пример реализации обобщенного интерфейса обобщенным

классом:

// реализация обобщенным классом class real<U> : IExample<U>

{

public char this[U r] {

get { return r.ToString()[0]} set{ } }

public int add(U a, U b)

{ return a.GetHashCode() + b.GetHashCode(); }

}

В реализации индексатор возвращает первый символ строкового представления индекса, использованного при обращении к индексатору. В качестве реализации акссесора set использована "заглушка". Реализация метода add(U a, U b)

выполняет суммирование хеш-кодов аргументов. Так как методы ToString() и GetHashCode() присущи всем классам языка C#, то в объявление обобщенного класса не потребовалось включать ограничения на типизирующий параметр.

Применение обобщенного класса real<U> иллюстрирует следующий код

(програама 18_04.cs): class Program { static void Main( ) {

var temp1 = new real<int>(); var temp2 = new real<byte>(); Console.WriteLine(temp1[655]);

Console.WriteLine(temp1.add(950,6));

Console.WriteLine(temp2[155]); Console.WriteLine(temp2.add(34, 6));

}

}

Результаты выполнения программы:

6

956

1

40

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

тривиальными реализациями членов интерфейсов (программа 18_05.cs): struct Realization : IExample<string>, IExample<double> {

public char this[string st] { get { return '#'; } set { }} public int add(string s1, string s2) { return -135; } public char this[double d] { get { return (char)d; } set { }}

public int add(double d1, double d2) {return (int)(d1-d2);}

}

В данном случае регулярная структура

struct Realization реализует два

интерфейса, сконструированных на основе обобщенного интерфейса interface

Соседние файлы в папке CSBasicCourse2ndedPodbelsky