![](/user_photo/2706_HbeT2.jpg)
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky
.pdfdouble (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