Полное руководство. С# 4.0
Шрифт:
В этой иерархии класс Gen2 наследует от обобщенного класса Gen. Обратите вни мание на объявление класса Gen2 в следующей строке кода. class Gen2<T> : Gen<T> {
Параметр типа Т указывается в объявлении класса Gen2 и в то же время передается классу Gen. Это означает, что любой тип, передаваемый классу Gen2, будет передавать ся также классу Gen. Например, в следующем объявлении: Gen2<string> g2 = new Gen2<string>("Привет");
параметр типа string передается классу Gen. Поэтому переменная ob в той части класса Gen2, которая относится к классу Gen, будет иметь тип string. Обратите также внимание на то, что в классе Gen2 параметр типа Т не использует ся, а только передается вверх по иерархии базовому классу Gen. Это означает, что в производном классе
Разумеется, в производный класс можно свободно добавлять его собственные па раметры типа, если в этом есть потребность. В качестве примера ниже приведен ва риант предыдущей иерархии классов, где в класс Gen2 добавлен собственный пара метр типа. // Пример добавления собственных параметров типа в производный класс. using System; // Обобщенный базовый класс. class Gen<T> { Т ob; // объявить переменную типа Т // Передать конструктору ссылку типа Т. public Gen(T о) { ob = о; } // Возвратить значение переменной ob. public Т GetOb { return ob; } } // Класс, производный от класса Gen. В этом классе // определяется второй параметр типа V. class Gen2<T, V> : Gen<T> { V ob2; public Gen2(T о, V o2) : base(о) { ob2 = o2; } public V GetObj2 { return ob2; } } // Создать объект класса Gen2. class GenHierDemo2 { static void Main { // Создать объект класса Gen2 с параметрами // типа string и int. Gen2<string, int> x = new Gen2<string, int>("Значение равно: ", 99); Console.Write(x.GetOb); Console.WriteLine(x.GetObj2); } }
Обратите внимание на приведенное ниже объявление класса 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>;