Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык C# и основы платформы .NET.docx
Скачиваний:
36
Добавлен:
11.05.2015
Размер:
178.68 Кб
Скачать

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

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

publicclassDictionary<K, V>

{

publicvoidAdd(K key, V value)

{

. . .

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

. . .

}

}

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

// переписана строка, вызывавшая ранее ошибку компиляции

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

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

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

Ограничения делятся на первичные ограничения, вторичные ограничения и ограничения конструктора. Первичное ограничение– это некий тип. Аргумент сконструированного типа должен приводиться к первичному ограничению. Для первичного ограничения нельзя использовать типы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. Универсальные методы

В некоторых случаях достаточно параметризировать не весь пользовательский тип, а только отдельный метод. Универсальные методы(generic methods) объявляются с использованием параметров-типов в угловых скобках после имени метода12. Как и при описании универсальных типов, универсальные методы могут содержать ограничения на параметр-тип.

voidPushMultiple<T>(Stack<T> stack,paramsT[] values)

{

foreach(T valueinvalues)

{

stack.Push(value);

}

}

Использование универсального метода PushMultiple<T>позволяет работать с любым сконструированным типом на основеStack<T>.

Stack<int> stack =newStack<int>();

PushMultiple<int>(stack, 1, 2, 3, 4);

В большинстве ситуаций компилятор способен самостоятельно вывести аргумент-тип универсального метода на основе анализа типов аргументов. Это позволяет записывать вызов метода без указания типа:

var stack = new Stack<int>();

PushMultiple(stack, 1, 2, 3, 4);

Рассмотрим следующий пример:

publicclassC

{

publicstaticvoidM(stringa,stringb) { . . . }

publicstaticvoidM<T>(T a, T b) { . . . }

}

inta = 10;

byteb = 20;

strings ="ABC";

C.M(a, b);// C.M<int>(a,b)

C.M(s, s);// C.M(s,s)

C.M<string>(s, s);// C.M<string>(s,s)

В первом случае компилятор сконструирует метод M<T>()какM<int>(), потому что к типуintприводится и переменнаяa, и переменнаяb. Второй вызов показывает, что при наличии альтернатив компилятор всегда выбирает версию метода без универсального параметра, если только метод не сконструирован явно.