Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Yazyk_C_i_osnovy_platformy_NET.docx
Скачиваний:
16
Добавлен:
11.05.2015
Размер:
198.13 Кб
Скачать

18.2. Ограничения на параметры универсальных типов

Как правило, универсальные типы не просто хранят данные, но и вызывают методы у объекта, чей тип указан как параметр. Например, в классе Dictionary<K, V>методAdd()может использовать методCompareTo()для сравнения ключей:

publicclassDictionary<K, V>

{

publicvoidAdd(K key, V value)

{

. . .

if(key.CompareTo(x) < 0) { . . . }// ошибка компиляции!

. . .

}

}

Ошибка компиляции в этом примере возникает по следующей причине. Так как тип Kможет быть любым, то у параметраkeyможно вызывать только методы, определённые вobject. Проблему можно решить, используя приведение типов:

publicclassDictionary<K, V>

{

publicvoidAdd(K key, V value)

{

. . .

if(((IComparable) key).CompareTo(x) < 0) { . . . }

. . .

}

}

Недостаток такого подхода – многочисленность операций приведения. К тому же, если у сконструированного типа параметр Kне поддерживает интерфейсIComparable, то при работе программы будет сгенерировано исключениеInvalidCastException.

C# допускает указание ограничений(constraints) для каждого параметра универсального типа. Только тип, удовлетворяющий ограничениям, может быть применён для записи сконструированного типа.

Ограничения делятся на первичные ограничения, вторичные ограничения и ограничения конструктора. Первичное ограничение– это тип, который не являетсяsealed, за исключениемSystem.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.EnumилиSystem.Void. Первичное ограничение требует, чтобы аргумент сконструированного типа приводился к указанному типу.

Существуют два особых первичных ограничения – classиstruct. Ограничениюclassудовлетворяет любой ссылочный тип – класс, интерфейс, делегат. Ограничениюstructудовлетворяет любой тип значения, за исключением типов с поддержкойnull.

Вторичное ограничение– это интерфейс. Вторичное ограничение требует, чтобы аргумент сконструированного типа реализовывал указанный интерфейс.

Ограничение конструктораимеет видnew()и требует, чтобы аргумент сконструированного типа имел конструктор без параметров.

Ограничения объявляются с использованием ключевого слова where, после которого указывается параметр универсального типа, двоеточие исписок ограничений:

– ноль или одно первичное ограничение;

– ноль или несколько вторичных ограничений;

– ноль или одно ограничение конструктора (если не задано первичное ограничение struct).

В следующем примере демонстрируется использование ограничений на различные параметры универсального типа:

publicclassEntityTable<K, E>

whereK :IComparable<K>,IPersistable

whereE :Entity,new()

{

publicvoidAdd(K key, E entity)

{

. . .

if (key.CompareTo(x) < 0) { . . . }

. . .

}

}

18.3. Ковариантность и контравариантность

Определим понятия ковариантности и контравариантности для сконструированных типов данных. Для этого введём отношение частичного порядка на множестве ссылочных типов:

.

Если имеется тип C<T>, а также типыT1иT2(T1 ≤ T2), тоC<T>назовём:

ковариантным, еслиC<T1> ≤ C<T2>;

контравариантным, еслиC<T2> ≤ C<T1>;

инвариантным, если не верно ни первое, ни второе утверждение.

Понятия частичного порядка типов, ковариантности и контравариантности связаны с приведением типов. Тот факт, что тип T1«меньше» типаT2, означает возможность неявного приведения переменной типаT1к типуT2. Как указывалось ранее, массивы ковариантны для ссылочных типов (например, массив строк присваивается массиву объектов).

Универсальные классы и структуры инвариантны, однако универсальные интерфейсы могут быть описаны как ковариантные или контравариантные относительно некоего параметра-типа. Чтобы указать на ковариантность относительно параметраT, следует использовать ключевое словоoutпри описании параметра типа. На контравариантность указывает ключевое словоinпри описании параметра типа.

publicinterfaceIOutOnly<outT>

{

T this[intindex] {get; }

}

publicinterfaceIInOnly<inT>

{

void Process(T x);

}

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