Выбрать главу

public void TestSum()

{

    SumList<string, int> list1 =

        new SumList<string, int>(new IntCalc());

    list1.add("Петр", 33); list1.add("Павел", 44);

    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.

Рис. 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.