КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Верификация и ограниченияВ процессе компиляции обобщенного кода компилятор 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 позволяют применять к параметрам-типам. К параметру-типу могут применяться следующие ограничения: основное, дополнительное и/или ограничение конструктора. Речь о них пойдет в следующих трех разделах.
|