Хотя перегрузка является полезным средством объектно-ориентированного языка, проблема заключается в том, что при этом довольно легко получить в итоге огромное количество методов, которые по существу делают одно и то же. Например, пусть необходимо создать методы, которые позволяют менять местами два фрагмента данных посредством простой процедуры. Вы можете начать с написания нового статического класса с методом, который способен оперировать целочисленными значениями:
using System;
namespace CustomGenericMethods
{
static class SwapFunctions
{
// Поменять местами два целочисленных значения.
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
}
}
Пока все идет хорошо. Но теперь предположим, что нужно менять местами также и два объекта Person; действие потребует написания новой версии метода Swap():
// Поменять местами два объекта Person.
static void Swap(ref Person a, ref Person b)
{
Person temp = a;
a = b;
b = temp;
}
Вне всяких сомнений вам должно быть ясно, чем все закончится. Если также понадобится менять местами два значения с плавающей точкой, два объекта растровых изображений, два объекта автомобилей, два объекта кнопок или что-нибудь еще, то придется писать дополнительные методы, что в итоге превратится в настоящий кошмар при сопровождении. Можно было бы построить один (необобщенный) метод, оперирующий с параметрами типа object, но тогда возвратятся все проблемы, которые были описаны ранее в главе, т.е. упаковка, распаковка, отсутствие безопасности в отношении типов, явное приведение и т.д.
Наличие группы перегруженных методов, отличающихся только входными аргументами — явный признак того, что обобщения могут облегчить ситуацию. Рассмотрим следующий обобщенный метод Swap, который способен менять местами два значения типа Т:
// Этот метод будет менять местами два элемента
// типа, указанного в параметре <Т>.
static void Swap
{
Console.WriteLine("You sent the Swap() method a {0}", typeof(T));
T temp = a;
a = b;
b = temp;
}
Обратите внимание, что обобщенный метод определен за счет указания параметра типа после имени метода, но перед списком параметров. Здесь заявлено, что метод Swap способен оперировать на любых двух параметрах типа <Т>. Для придания некоторой пикантности имя замещаемого типа выводится на консоль с использованием операции typeof() языка С#. Взгляните на показанный ниже вызывающий код, который меняет местами целочисленные и строковые значения:
Console.WriteLine("***** Fun with Custom Generic Methods *****\n");
// Поменять местами два целочисленных значения.
int a = 10, b = 90;
Console.WriteLine("Before swap: {0}, {1}", a, b);
SwapFunctions.Swap
Console.WriteLine("After swap: {0}, {1}", a, b);
Console.WriteLine();
// Поменять местами два строковых значения.
string s1 = "Hello", s2 = "There";
Console.WriteLine("Before swap: {0} {1}!", s1, s2);
SwapFunctions.Swap
Console.WriteLine("After swap: {0} {1}!", s1, s2);
Console.ReadLine();
Вот вывод:
***** Fun with Custom Generic Methods *****
Before swap: 10, 90
You sent the Swap() method a System.Int32
After swap: 90, 10
Before swap: Hello There!
You sent the Swap() method a System.String
After swap: There Hello!
Главное преимущество такого подхода в том, что придется сопровождать только одну версию Swap, однако она в состоянии работать с любыми двумя элементами заданного типа в безопасной в отношении типов манере. Еще лучше то, что находящиеся в стеке элементы остаются в стеке, а расположенные в куче — соответственно в куче.
Выведение параметров типа