Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
Console.WriteLine("sum= {0}", list1.Sum);
SumList<string, double> list2 =
new SumList<string, double> (new DoubleCalc);
list2.add("Петр", 33.33); list2.add("Павел", 44.44);
Console.WriteLine("sum= {0}", list2.Sum);
SumList<string, string> list3 =
new SumList<string, string> (new StringCalc);
list3.add("Мама", " Мама мыла "); list3.add("Маша",
"Машу мылом!");
Console.WriteLine("sum= {0}", list3.Sum );
}
Обратите внимание на создание списков:
SumList<string, int> list1 =
new SumList<string, int>(new IntCalc);
SumList<string, double> list2 =
new SumList<string, double>(new DoubleCalc);
SumList<string, string> list3 =
new SumList<string, string>(new StringCalc);
Как
Рис. 22.6. Списки с суммированием
Родовое порождение класса. Предложение using
До сих пор рассматривалась ситуация родового порождения экземпляров универсального класса. Фактические типы задавались в момент создания экземпляра. Это наглядно показывает преимущества применяемой технологии, поскольку очевидно, что не создается дублирующий код для каждого класса, порожденного универсальным классом. И все-таки остается естественный вопрос: можно ли породить класс из универсального класса путем подстановки фактических параметров, а потом спокойно использовать этот класс обычным образом? Такая вещь возможна. Это можно сделать не совсем обычным путем — не в программном коде, а в предложении using, назначение которого и состоит в выполнении подобных подстановок.
Давайте вернемся к универсальному классу OneLinkstack<T>, введенному в начале этой лекции, и породим на его основе вполне конкретный класс IntStack, заменив формальный параметр T фактическим — int. Для этого достаточно задать следующее предложение using;
using IntStack = Generic.OneLinkStack<int>;
Вот тест, в котором создаются несколько объектов этого класса:
public void TestlntStack
{
IntStack stack1 = new IntStack ;
IntStack stack2 = new IntStack ;
IntStack stack3 = new IntStack ;
stack2.put (11); stackl.put (22);
int x1 = stack1.item, x2 = stack1.item;
if ((x1 == x2) && (xl == 22)) Console.WriteLine("OK!");
stack1.remove; x2 = stack1.item;
if ((x1!= x2) && (x2 == 11)) Console.WriteLine("OK!");
stack1.remove; x2 = (stack1.empty)? 77:
stack1.item;
if ((x1!= x2) && (x2 == 77)) Console.WriteLine("OK!");
stack2.put (55); stack2.put (66);
stack2.remove; int s = stack2.item;
if (!stack2.empty) Console.WriteLine(s);
stack3.put (33 3); stack3.put((int)Math.Sqrt(Math.PI));
int res = stack3.item;
stack3.remove; res += stack3.item;
Console.WriteLine("res= {0}", res);
}
Все работает заданным образом, можете поверить.
Универсальность и специальные случаи классов
Универсальность — это механизм, воздействующий на все элементы языка. Поэтому он применим ко всем частным случаям классов C#.
Универсальные структуры
Так же, как и обычный класс, структура может иметь родовые параметры. Синтаксис объявления, ограниченная универсальность, другие детали универсальности естественным образом распространяются на структуры. Вот типичный пример:
public struct Point<T>
{
Т х, у;//координаты точки, тип которых задан параметром
// другие свойства и методы структуры
}
Универсальные интерфейсы
Интерфейсы чаще всего следует делать универсальными, предоставляя большую гибкость для позднейших этапов создания системы. Возможно, вы заметили применение в наших примерах универсальных интерфейсов библиотеки FCL — IСоmраrаЫе<T> и других. Введение универсальности, в первую очередь, сказалось на библиотеке FCL — внутренних классов, определяющих поведение системы. В частности, для большинства интерфейсов появились универсальные двойники с параметрами. Если бы в наших примерах мы использовали не универсальный интерфейс, а обычный, то потеряли бы в эффективности, поскольку сравнение объектов потребовало бы создание временных объектов типа object, выполнения операций boxing и unboxing.
Универсальные делегаты
Делегаты также могут иметь родовые параметры. Чаще встречается ситуация, когда делегат объявляется в универсальном классе и использует в своем объявлении параметры универсального класса. Давайте рассмотрим ситуацию с делегатами более подробно. Вот объявление универсального класса, не очень удачно названного Delegate, в котором объявляется функциональный тип — delegate;
class Delegate<T>
{
public delegate T Del(T a, T b);
}
Как видите, тип аргументов и возвращаемого значения в сигнатуре функционального типа определяется классом Delegate.
Добавим в класс функцию высшего порядка FunAr, одним из аргументов которой будет функция типа Del, заданного делегатом. Эта функция будет применяться к элементам массива, передаваемого также функции FunAr. Приведу описание:
public T FunAr(T[] arr, T a0, Del f)
{
T temp = a 0;
for (int i =0; i<arr.Length; i + +)
{
temp = f(temp, arr[i]);
}
return (temp);
}
Эта универсальная функция с успехом может применяться для вычисления сумм, произведения, минимума и других подобных характеристик массива.
Рассмотрим теперь клиентский класс Testing, в котором определен набор функций:
public int max2(int a, int b)
{ return (a > b)? a: b; }
public double min2(double a, double b)