Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
Параметр типа объявляется после имени метода, но перед списком его параметров.Обратите также внимание на то, что метод CopyInsert() является статическим, чтопозволяет вызывать его независимо от любого объекта. Следует, однако, иметь в виду,что обобщенные методы могут быть либо статическими, либо нестатическими. В этомотношении для их не существует никаких ограничений.
Далее обратите внимание на то, что метод CopyInsert() вызывается в методеMain() с помощью обычного синтаксиса и без указания аргументов типа. Дело в том,что типы аргументов различаются автоматически, а тип Т соответственно подстраивается. Этот процесс называется выводимостью типов. Например, в первом вызове данного методаArrayUtils.CopyInsert(99, 2, nums, nums2);
тип T становится типом int, поскольку числовое значение 99 и элементы массивовnums и nums2 относятся к типу int. А во втором вызове данного метода используютсястроковые типы, и поэтому тип Т заменяется типом string.
А теперь обратите внимание на приведенную ниже закомментированную строкукода.// ArrayUtils.CopyInsert(0.01, 2, nums, nums2);
Если удалить символы комментария в начале этой строки кода и затем попытатьсяперекомпилировать программу, то будет получено сообщение об ошибке. Дело в том,что первый аргумент в данном вызове метода CopyInsert() относится к типу double,а третий и четвертый аргументы обозначают элементы массивов nums и nums2 типаint. Но все эти аргументы типа должны заменить один и тот же параметр типа Т,а это приведет к несоответствию типов и, как следствие, к ошибке во время компиляции. Подобная возможность соблюдать типовую безопасность относится к одним изсамых главных преимуществ обобщенных методов.
Синтаксис объявления метода CopyInsert() может быть обобщен. Ниже приведена общая форма объявления обобщенного метода.возвращаемый_тип имя_метода<список_параметров_типа>(список_параметров) { // ...
В любом случае списокпараметровтипа обозначает разделяемый запятой список параметров типа. Обратите внимание на то, что в объявлении обобщенного методасписок параметров типа следует после имени метода.Вызов обобщенного метода с явно указанными аргументами типа
В большинстве случаев неявной выводимости типов оказывается достаточно для вызова обобщенного метода, тем не менее аргументы типа могут быть указаны явным образом. Для этого достаточно указать аргументы типа после имени метода при его вызове. В качестве примера ниже приведена строка кода, в которой метод CopyInsert()вызывается с явно указываемым аргументом типа string.ArrayUtils.CopyInsert<string>("В С#", 1, strs, strs2);
Тип передаваемых аргументов необходимо указывать явно в том случае, если компилятор не сможет вывести тип параметра Т или если требуется отменить выводимость типов.Применение ограничений в обобщенных методах
На аргументы обобщенного метода можно наложить ограничения, указав их послесписка параметров. В качестве примера ниже приведен вариант метода CopyInsert()для обработки данных только ссылочных типов.public static bool CopyInsert<T>(Т e, uint idx, T[] src, T[] target) where T : class {
Если попробовать применить этот вариант в предыдущем примере программы обработки массивов, то приведенный ниже вызов метода CopyInsert() не будет скомпилирован, поскольку int является типом значения, а не ссылочным типом.// Теперь неправильно, поскольку параметр Т должен быть ссылочного типа!ArrayUtils.Copylnsert(99, 2, nums, nums2); // Теперь недопустимо!Обобщенные делегаты
Как и методы, делегаты также могут быть обобщенными. Ниже приведена общаяформа объявления обобщенного делегата.delegate возврашдемый_тип имя_делегата<список_параметров_типа>(список_аргументов);
Обратите внимание на расположение списка параметров типа. Он следует непосредственно после имени делегата. Преимущество обобщенных делегатов заключаетсяв том, что их допускается определять в типизированной обобщенной форме, которуюможно затем согласовать с любым совместимым методом.
В приведенном ниже примере программы демонстрируется применение делегатаSomeOp с одним параметром типа Т. Этот делегат возвращает значение типа Т и принимает аргумент типа Т.// Простой пример обобщенного делегата.using System;// Объявить обобщенный делегат.delegate Т SomeOp<T>(T v);class GenDelegateDemo { // Возвратить результат суммирования аргумента. static int Sum(int v) { int result = 0; for(int i=v; i>0; i--) result += i; return result; } // Возвратить строку, содержащую обратное значение аргумента. static string Reflect(string str) { string result = ""; foreach(char ch in str) result = ch + result; return result; } static void Main() { // Сконструировать делегат типа int. SomeOp<int> intDel = Sum; Console.WriteLine(intDel(3)); // Сконструировать делегат типа string. SomeOp<string> strDel = Reflect; Console.WriteLine(strDel("Привет")); }}
Эта программа дает следующий результат.6тевирП
Рассмотрим эту программу более подробно. Прежде всего обратите внимание наследующее объявление делегата SomeOp.delegate Т SomeOp<T>(Т v);
Как видите, тип Т может служить в качестве возвращаемого типа, несмотря на то,что параметр типа Т указывается после имени делегата SomeOp.
Далее в классе GenDelegateDemo объявляются методы Sum() и Reflect(), как показано ниже.static int Sum(int v) {static string Reflect(string str) {
Метод Sum() возвращает результат суммирования целого значения, передаваемогов качестве аргумента, а метод Reflect() — символьную строку, которая получается обращенной по отношению к строке, передаваемой в качестве аргумента.
В методе Main() создается экземпляр intDel делегата, которому присваиваетсяссылка на метод Sum().SomeOp<int> intDel = Sum;
Метод Sum() принимает аргумент типа int и возвращает значение типа int, поэтому он совместим с целочисленным экземпляром делегата SomeOp.
Аналогичным образом создается экземпляр strDel делегата, которому присваивается ссылка на метод Reflect().SomeOp<string> strDel = Reflect;
Метод Reflect() принимает аргумент типа string и возвращает результат типаstring, поэтому он совместим со строковым экземпляром делегата SomeOp.В силу присущей обобщениям типовой безопасности обобщенным делегатам нельзя присваивать несовместимые методы. Так, следующая строка кода оказалась бы ошибочной в рассматриваемой здесь программе.SomeOp<int> intDel = Reflect; // Ошибка!
Ведь метод Reflect() принимает аргумент типа string и возвращает результаттипа string, а следовательно, он несовместим с целочисленным экземпляром делегата SomeOp.Обобщенные интерфейсы
Помимо обобщенных классов и методов, в C# допускаются обобщенные интерфейсы. Такие интерфейсы указываются аналогично обобщенным классам. Ниже приведенизмененный вариант примера из главы 12, демонстрирующего интерфейс ISeries.(Напомним, что ISeries является интерфейсом для класса, генерирующего последовательный ряд числовых значений.) Тип данных, которым оперирует этот интерфейс,теперь определяется параметром типа.// Продемонстрировать применение обобщенного интерфейса.using System;public interface ISeries<T> { T GetNext();// возвратить следующее по порядку число void Reset(); // генерировать ряд последовательных чисел с самого начала void SetStart(Т v); // задать начальное значение}// Реализовать интерфейс ISeries.class ByTwos<T> : ISeries<T> { T start; T val; // Этот делегат определяет форму метода, вызываемого для генерирования // очередного элемента в ряду последовательных значений. public delegate Т IncByTwo(T v); // Этой ссылке на делегат будет присвоен метод, // передаваемый конструктору класса ByTwos. IncByTwo incr; public ByTwos(IncByTwo incrMeth) { start = default(T); val = default(T); incr = incrMeth; } public T GetNext() { val = incr(val); return val; } public void Reset() { val = start; } public void SetStart(T v) { start = v; val = start; }}class ThreeD { public int x, y, z; public ThreeD(int a, int b, int c) { x = a; У = b; z = c; }}class GenlntfDemo { // Определить метод увеличения на два каждого // последующего значения типа int. static int IntPlusTwo (int v) { return v + 2; } // Определить метод увеличения на два каждого // последующего значения типа double. static double DoublePlusTwo (double v) { return v + 2.0; } // Определить метод увеличения на два каждого // последующего значения координат объекта типа ThreeD. static ThreeD ThreeDPlusTwo(ThreeD v) { if(v==null) return new ThreeD(0, 0, 0); else return new ThreeD(v.x + 2, v.y + 2, v.z + 2); } static void Main() { // Продемонстрировать генерирование // последовательного ряда значений типа int. ByTwos<int> intBT = new ByTwos<int>(IntPlusTwo); for(int i=0; i < 5; i++) Console.Write(intBT.GetNext() + " "); Console.WriteLine(); // Продемонстрировать генерирование // последовательного ряда значений типа double. ByTwos<double> dblBT = new ByTwos<double>(DoublePlusTwo); dblBT.SetStart(11.4); for (int i=0; i < 5; i++) Console.Write(dblBT.GetNext() + " "); Console.WriteLine(); // Продемонстрировать генерирование последовательного ряда // значений координат объекта типа ThreeD. ByTwos<ThreeD> ThrDBT = new ByTwos<ThreeD>(ThreeDPlusTwo); ThreeD coord; for(int i=0; i < 5; i++) { coord = ThrDBT.GetNext(); Console.Write(coord.x + "," + coord.у + "," + coord.z + " "); } Console.WriteLine(); }}