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

18.3.Универсальные методы

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

тоды могут содержать ограничения на параметр-тип.

void PushMultiple<T>(Stack<T> stack, params T[] values)

{

foreach (T value in values)

{

stack.Push(value);

}

}

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

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

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

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

var stack = new Stack<int>(); PushMultiple(stack, 1, 2, 3, 4);

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

public class C

{

public static void M(string a, string b) { . . . } public static void M<T>(T a, T b) { . . . }

}

 

int a = 10;

 

byte b = 20;

 

string s = "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)

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

2Хотя свойства и индексаторы транслируются в вызов методов, в языке C# не существует универсальных свойств и универсальных индексаторов.

53

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

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

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

1 2 1 наследуется (прямо или косвенно) от 2.

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

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

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

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

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

Покажем на примере, что универсальные классы не могут быть ни ковариантными, ни контравариантными. Рассмотрим следующий код:

// два простых класса, связанных наследованием public class Person

{

public string Name;

}

public class Student : Person

{

public string University;

}

// универсальный класс с открытым полем public class C<T>

{

public T Field;

}

//использование классов var cp = new C<Person>();

var cs = new C<Student> {Field = new Student()};

//закомментировано присваивание, возможное при ковариантности

//cp = cs;

cp.Field = new Person();

54