Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
Обратите внимание на приведенное ниже объявление класса Gen2 в данном варианте иерархии классов.class Gen2<T, V> : Gen<T> {
В этом объявлении Т — это тип, передаваемый базовому классу Gen; а V — тип, характерный только для производного класса Gen2. Он служит для объявления объектаоb2 и в качестве типа, возвращаемого методом GetObj2(). В методе Main() создаетсяобъект класса Gen2 с параметром Т типа string и параметром V типа int. Поэтомукод из приведенного выше примера дает следующий результат.Значение равно: 99Обобщенный производный класс
Необобщенный класс может быть вполне законно базовым для обобщенного производного класса. В качестве примера рассмотрим следующую программу.// Пример необобщенного класса в качестве базового для// обобщенного производного класса.using System;// Необобщенный базовый класс.class NonGen { int num; public NonGen(int i) { num = i; } public int GetNum() { return num; }}// Обобщенный производный класс.class Gen<T> : NonGen { T ob; public Gen(T о, int i) : base (i) { ob = o; } // Возвратить значение переменной ob. public T GetOb() { return ob; }}// Создать объект класса Gen.class HierDemo3 { static void Main() { // Создать объект класса Gen с параметром типа string. Gen<String> w = new Gen<String>("Привет", 47); Console.Write(w.GetOb() + " "); Console.WriteLine(w.GetNum()); }}
Эта программа дает следующий результат.Привет 47
В данной программе обратите внимание на то, как класс Gen наследует от классаNonGen в следующем объявлении.class Gen<T> : NonGen {
Класс NonGen не является обобщенным, и поэтому аргумент типа для него не указывается. Это означает, что параметр т, указываемый в объявлении обобщенного производного класса Gen, не требуется для указания базового класса NonGen и даже неможет в нем использоваться. Следовательно, класс Gen наследует от класса NonGenобычным образом, т.е. без выполнения каких-то особых условий.Переопределение виртуальных методов в обобщенном классе
В обобщенном классе виртуальный метод может быть переопределен таким же образом, как и любой другой метод. В качестве примера рассмотрим следующую программу, в которой переопределяется виртуальный метод GetOb().// Пример переопределения виртуального метода в обобщенном классе.using System;// Обобщенный базовый класс.class Gen<T> { protected Т ob; public Gen(T о) { ob = о; } // Возвратить значение переменной ob. Этот метод является виртуальным. public virtual T GetOb() { Console.Write("Метод GetOb() из класса Gen" + " возвращает результат: "); return ob; }}// Класс, производный от класса Gen. В этом классе// переопределяется метод GetOb().class Gen2<T> : Gen<T> { public Gen2 (T o) : base(o) { } // Переопределить метод GetOb(). public override T GetOb() { Console.Write("Метод GetOb() из класса Gen2" + " возвращает результат: "); return ob; }}// Продемонстрировать переопределение метода в обобщенном классе.class OverrideDemo { static void Main() { // Создать объект класса Gen с параметром типа int. Gen<int> iOb = new Gen<int>(88); // Здесь вызывается вариант метода GetOb() из класса Gen. Console.WriteLine(iOb.GetOb()); // А теперь создать объект класса Gen2 и присвоить // ссылку на него переменной iOb типа Gen<int>. iOb = new Gen2<int>(99); // Здесь вызывается вариант метода GetOb() из класса Gen2. Console.WriteLine(iOb.GetOb()); }}
Ниже приведен результат выполнения этой программы.Метод GetOb() из класса Gen возвращает результат: 88Метод GetOb() из класса Gen2 возвращает результат: 99
Как следует из результата выполнения приведенной выше программы, переопределяемый вариант метода GetOb() вызывается для объекта типа Gen2, а его вариант избазового класса вызывается для объекта типа Gen.
Обратите внимание на следующую строку кода.iOb = new Gen2<int>(99);
Такое присваивание вполне допустимо, поскольку iOb является переменной типаGen. Следовательно, она может ссылаться на любой объект типа Gen илиже объект класса, производного от Gen, включая и Gen2. Разумеется, переменную iOb нельзя использовать, например, для ссылки на объект типа Gen2,поскольку это может привести к несоответствию типов.Перегрузка методов с несколькими параметрами типа
Методы, параметры которых объявляются с помощью параметров типа, могут бытьперегружены. Но правила их перегрузки упрощаются по сравнению с методами безпараметров типа. Как правило, метод, в котором параметр типа служит для указаниятипа данных параметра этого метода, может быть перегружен при условии, что сигнатуры обоих его вариантов отличаются. Это означает, что оба варианта перегружаемого метода должны отличаться по типу или количеству их параметров. Но типовыеразличия должны определяться не по параметру обобщенного типа, а исходя из аргумента типа, подставляемого вместо параметра типа при конструировании объектаэтого типа. Следовательно, метод с параметрами типа может быть перегружен такимобразом, что он окажется пригодным не для всех возможных случаев, хотя и будет выглядеть верно.
В качестве примера рассмотрим следующий обобщенный класс.// Пример неоднозначности, к которой может привести// перегрузка методов с параметрами типа.//// Этот код не подлежит компиляции.using System;// Обобщенный класс, содержащий метод Set(), перегрузка// которого может привести к неоднозначности.class Gen<T, V> { Т оb1; V ob2; // ... // В некоторых случаях эти два метода не будут // отличаться своими параметрами типа. public void Set(T о) { ob1 = о; } public void Set(V о) { ob2 = о; }}class AmbiguityDemo { static void Main() { Gen<int, double> ok = new Gen<int, double>(); Gen<int, int> notOK = new Gen<int, int>(); ok.Set(10); // верно, поскольку аргументы типа отличаются notOK.Set(10); // неоднозначно, поскольку аргументы ничем не отличаются! }}
Рассмотрим приведенный выше код более подробно. Прежде всего обратите внимание на то, что класс Gen объявляется с двумя параметрами типа: Т и V. В классе Genметод Set() перегружается по параметрам типа Т и V, как показано ниже.public void Set(T о) { ob1 = о;}public void Set(V о) { ob2 = о;}
Такой подход кажется вполне обоснованным, поскольку типы Т и V ничем внешнене отличаются. Но подобная перегрузка таит в себе потенциальную неоднозначность.При таком объявлении класса Gen не соблюдается никаких требований к различению типов Т и V. Например, нет ничего принципиально неправильного в том, чтообъект класса Gen будет сконструирован так, как показано ниже.Ger<int, int> notOK = new Gen<int, int>();
В данном случае оба типа, Т и V, заменяются типом int. В итоге оба варианта метода Set() оказываются совершенно одинаковыми, что, разумеется, приводит к ошибке.Следовательно, при последующей попытке вызвать метод Set() для объекта notOK вметоде Main() появится сообщение об ошибке вследствие неоднозначности во времякомпиляции.
Как правило, методы с параметрами типа перегружаются при условии, что объектконструируемого типа не приводит к конфликту. Следует, однако, иметь в виду, чтоограничения на типы не учитываются при разрешении конфликтов, возникающих приперегрузке методов. Поэтому ограничения на типы нельзя использовать для исключения неоднозначности. Конструкторы, операторы и индексаторы с параметрами типамогут быть перегружены аналогично конструкторам по тем же самым правилам.Ковариантность и контравариантность в параметрах обобщенного типа
В главе 15 ковариантность и контравариантность были рассмотрены в связи с необобщенными делегатами. Эта форма ковариантности и контравариантности по-прежнему поддерживается в С#, поскольку она очень полезна. Но в версии C# 4.0 возможности ковариантности и контравариантности были расширены до параметровобобщенного типа, применяемых в обобщенных интерфейсах и делегатах. Ковариантность и контравариантность применяется, главным образом, для рационального разрешения особых ситуаций, возникающих в связи с применением обобщенных интерфейсов и делегатов, определенных в среде .NET Framework. И поэтому некоторые интерфейсы и делегаты, определенные в библиотеке, были обновлены, чтобы использоватьковариантность и контравариантность параметров типа. Разумеется, преимуществамиковариантности и контравариантности можно также воспользоваться в интерфейсах иделегатах, создаваемых собственными силами. В этом разделе механизмы ковариантности и контравариантности параметров типа поясняются на конкретных примерах.Применение ковариантности в обобщенном интерфейсе