Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
68
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

Обобщения и другие члены

В C# у свойств, индексаторов, событий, методов операторов, конструкторов и деструкторов не может быть параметров-типов. Но их можно определить в обобщенном типе с тем, чтобы в коде этих членов использовать параметры-типы этого типа.

С# не поддерживает задание собственных обобщенных параметров-типов у этих членов, поскольку создатели C# считают, что разработчикам вряд ли потребуется использовать эти члены в качестве обобщенных. Вдобавок, чтобы эти члены могли применяться как обобщенные, дляC# пришлось бы разработать специальный синтаксис, что довольно затратно. Например, при использовании в коде оператора «+» компилятор вызывает метод перегрузки оператора. В коде, где есть оператор «+», нельзя указать никакие аргументы-типы.

Верификация и ограничения

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

private static Boolean MethodTakingAnyType<T>(T o)

{

T temp = o;

Console.WriteLine(o.ToString());

Boolean b = temp.Equals(o);

return b;

}

Здесь объявляется временная переменная (temp) типа Т, а затем выполняется несколько операций присвоения переменных и несколько вызовов методов. Он работает с любым типом T— ссылочным, значимым, перечислимым, типом-интерфейсом или типом-делегатом, существующим типом или типом, который определят в будущем, — потому что любой тип поддерживает присвоения и вызовы методов, определенных в Object (например, ToString и Equals).

Вот еще метод:

private static T Min<T>(T o1, T o2)

{

if (o1.CompareTo(o2) < 0)

return o1;

return o2;

}

Метод Min пытается через переменную o1 вызвать методCompareTo. Но многие типы не поддерживают методCompareTo, поэтому компиляторC# не в состоянии скомпилировать этот код и обеспечить, чтобы после компиляции метод смог работать со всеми типами. При попытке скомпилировать приведенный выше код появится сообщение об ошибке:

«Error CS0117: "T does not contain a definition for ' CompareTo »

«ошибка CS0117: T не содержит определение метода CompareTo»

Поэтому может показаться, что при использовании обобщений можно лишь объявлять переменные обобщенного типа, назначать переменные, вызывать методы, определенные Object, и все! Но ведь в таком случае от обобщений пользы мало. К счастью, компиляторы и CLR поддерживают механизм под названием ограничения (constraints), благодаря которому обобщения успешно «реабилитируются».

Ограничение позволяет сузить перечень типов, которые можно передать в обобщенном аргументе, и расширяет возможности по работе с этими типами. Вот новый вариант метода Min, который задает ограничение (выделено серым):

private static T Min<T>(T o1, T o2) where T : IComparable<T>

{

if (o1.CompareTo(o2) < 0)

return o1;

return o2;

}

Маркер where в C# сообщает компилятору, что указанный вTтип должен реализовывать обобщенный интерфейс IComparable того же типа (T). Благодаря этому ограничению компилятор разрешает методу вызвать метод CompareTo, потому что последний определен в интерфейсе IComparable<T>.

Теперь, когда код ссылается на обобщенный тип или метод, компилятор должен убедиться, что в коде указан аргумент-тип, удовлетворяющий этим ограничениям. Например, при компиляции следующего кода появляется сообщение:

«Error CS0309: The type 'object' must be convertible to 'System.IComparable<object>' in order to use it as a parameter T in the generic type or method 'Program.Min<T>(T, T)'|»

«Ошибка CS0309: тип object необходимо преобразовать в System.IComparable<object>, чтобы его можно было использовать в качестве параметра T в обобщенном типе или методе Program.Min<T>(T, T)».

private static void CallMin()

{

Object o1 = "Jeff", o2 = "Richter";

Object oMin = Min<Object>(o1, o2); // Ошибка CS0309.

}

Компилятор выдает эту ошибку, потому что System.Object не реализует интерфейс IComparable<Object>. Честно говоря, System.Object вообще не реализует никаких интерфейсов.

Вы получили общее представление об ограничениях и их работе. Познакомимся с ними поближе. Ограничения можно применять к параметрам-типам как обобщенных типов, так и обобщенных методов (как показано в методе Min). CLR не поддерживает перегрузку по именам параметров-типов или ограничений. Перегрузка типов и методов выполняется только по арности. Покажу это на примере.

// Можно определить следующие типы:

internal sealed class AType { }

internal sealed class AType<T> { }

internal sealed class AType<T1, T2> { }

// Ошибка: конфликт с AType<T>, у которого нет ограничений,

internal sealed class АТуре<Т> where T : IComparable<T> {}

// Ошибка: конфликт с АТуре<Т1, Т2>.

internal sealed class АТуре<ТЗ, Т4> {}

internal sealed class AnotherType

{

// Можно определить следующие методы:

private static void M() {}

private static void М<Т>() { }

private static void M<T1, T2>() { }

// Ошибка: конфликт с M<T>, у которого нет ограничений.

private static void М<Т>() where T : IComparable<T> {}

// Ошибка: конфликт с М<Т1, Т2>.

private static void М<ТЗ, Т4>() {}

}

При переопределении виртуального обобщенного метода в переопределяющем методе нужно задавать то же число параметров-типов, а они, в свою очередь, наследуют ограничения, заданные для них методом базового класса. Честно говоря, переопределяемый метод вообще не вправе задавать ограничения для своих параметров-типов, но может переименовывать параметры-типы. Аналогично, при реализации интерфейсного метода в нем должно задаваться то же число параметров-типов, что и в интерфейсном методе, причем эти параметры-типы наследуют ограничения, заданные для них методом интерфейса. Следующий пример демонстрирует это правило с помощью виртуальных методов.

internal class Base

{

public virtual void M<T1, T2>()

where T1 : struct

where T2 : class { }

}

internal sealed class Derived : Base

{

public override void M<T3, T4>()

where T3 : EventArgs // Ошибка.

where T4 : class { } // Ошибка.

}

При компиляции этого кода появится сообщение об ошибке:

«Error CS0460: Constraints for override and explicit interface implementation methods are inherited from the base method so cannot be specified directly»

«Ошибка CS0460: ограничения для методов интерфейсов с переопределением и явной реализацией наследуются от базового метода и поэтому не могут быть заданы явно».

Если из метода М<ТЗ, Т4> класса Derived убрать две строки where, код успешно скомпилируется. Заметьте: разрешается переименовывать параметры-типы (в этом примере 77 изменено на ТЗ, а 72 на Т4), но изменять (и даже задавать) ограничения нельзя.

Теперь поговорим о различных типах ограничений, которые компилятор и CLR позволяют применять к параметрам-типам. К параметру-типу могут применяться следующие ограничения: основное, дополнительное и/или ограничение конструктора. Речь о них пойдет в следующих трех разделах.