Российский Государственный Технологический Университет им. К.Э. Циолковского
Опубликован: 02.03.2007 | Доступ: свободный | Студентов: 5636 / 790 | Оценка: 3.96 / 3.45 | Длительность: 27:04:00
ISBN: 978-5-9556-0086-4
Лекция 13:

Шаблоны

Шаблоны классов и функций. Ограничения параметра типа

До настоящего момента обсуждались вопросы построения шаблонных классов на основе предопределенных (включенных в состав библиотеки классов) шаблонов классов. В этом разделе на простом примере обсуждается техника объявления собственных шаблонов классов и шаблонов функций.

Шаблонный класс обеспечивает стандартную реализацию дополнительной функциональности на основе ранее объявленных подстановочных классов.

Эта дополнительная функциональность может накладывать дополнительные ограничения на подстановочный класс. Например, для успешной работы объекта шаблонного класса подстановочный класс должен наследовать определенному интерфейсу. Иначе функциональность шаблонного класса просто невозможно будет реализовать.

Для формирования ограничений на подстановочные классы в C# используется механизм ограничителей параметров шаблона — он вводится при объявлении шаблона с помощью ключевого слова where, за которым могут располагаться имя параметра типа и список типов класса или интерфейса либо конструктор – ограничение new():

using System;
 using System.Collections;
 using System.Collections.Generic;

namespace PatternArrays
 {

 //========== Это заголовок шаблона класса W ==========
 // Шаблон класса своими руками. T – параметр шаблона.
 // Шаблонный класс –  это класс-шаблон, который детализируется
 // подстановочным классом.
 // При создании шаблонного класса вхождения параметра шаблона
 // (в данном случае это T) замещаются именем подстановочного
 // класса. Разработчик шаблона класса может выдвигать требования
 // относительно характеристик подстановочного класса.
 // Для этого используются специальные языковые конструкции,
 // называемые ОГРАНИЧИТЕЛЯМИ ПАРАМЕТРА ШАБЛОНА.
 // ОГРАНИЧИТЕЛЬ ПАРАМЕТРА ШАБЛОНА формулирует требования для
 // подстановочного класса.
 class W<T> where T: IComparable, new()
 //         	 	Ограничитель параметра шаблона new() – особый
 //        		 	ограничитель.
 //        	  		Во-первых, в списке ограничителей шаблона
 //          		он всегда последний.
 //          		Во-вторых, этот ограничитель НЕ ограничивает.
 //             	Он ОБЕСПЕЧИВАЕТ обязательное выполнение явно
 //             	заданного конструктора умолчания для 
 //             	подстановочного класса в шаблонном
 //             	классе. Это единственный способ заставить
 //             	выполниться конструктор умолчания
 //             	подстановочного класса при создании
 //             	объекта шаблонного класса. 
 //             	Ограничитель подстановочного класса.
 //          	Шаблонный класс строится на основе шаблона и множества
 //           	подстановочных классов, которыми замещаются параметры
 //           	шаблона. Таким образом ограничители подстановочного 
 //          	класса формулируют требования по поводу "родословной"
 //           	подстановочного класса.
 //           	В данном случае претендент на замещение параметра T
 //          	в шаблоне W обязательно должен наследовать интерфейс
 //          	IComparable. 
 {

 // Вот место, которое предназначено объекту подстановочного класса.
 // Объект - представитель шаблонного класса включает объект, 
 // представляющий подстановочный класс. 
public T t;

 // Конструктор шаблона.
 // Вот по какой схеме производится встраивание объекта-представителя
 // подстановочного класса. Всего лишь для того, чтобы эта схема
 // построения объекта - представителя шаблонного класса работала, 
 // в объявлении шаблона должен присутствовать ограничитель параметра
 // шаблона new(). Его отсутствие приводит к возникновению ошибки 
 // компиляции. С каких это пор необходимое требование стали 
 // называть ограничением?
  public W()
  {
      t = new T();
  }
  
 // Сравнение объектов в шаблоне. Обращение к функциям сравнения
 // регламентировано стандартными интерфейсами.
 // Полиморфизм через интерфейсы в действии.
  public int wCompare(T t) 
 {
   return ((IComparable)this.t).CompareTo(t);
  }

 // А вот замечательный шаблон функции.
 // Он реализован в рамках класса-шаблона W.
 // Эта функция предназначена для формирования шаблонных очередей
 // из входных массивов объектов - представителей подстановочного
 // класса, представленного параметром шаблона Z.
 // Между прочим, такое обозначение параметра ничуть не хуже любого 
 // другого. Более того, если бы здесь было использовано старое 
 // обозначение параметра, транслятор выступил бы с предупреждением 
 // по поводу того, что две разных сущности (параметр шаблона для 
 // шаблона класса и параметр шаблона для параметра функции) 
 // в рамках одного и того же объявления имеют одинаковые обозначения.
  public void QueueFormer<Z>(Queue<Z> queue, params Z[] values)
  {
      foreach (Z z in values)
      {
          queue.Enqueue(z); 
     }
  }
 }

 //===============================================================
 // Вот классы-кандидаты на подстановку в шаблон.
 // Первый класс подходит, а второй –  не подходит!
 // Все решается на этапе трансляции.
 //===============================================================
  class xPoints : IComparable
 {
     // Объект-генератор "случайных" чисел. 
    static Random rnd = new Random();

    public int x;
     public int y;

    public xPoints()
     {
         x = rnd.Next(0, 100);
         y = rnd.Next(0, 100);
     }

     // Ничто не может помешать классу иметь
      // различные версии конструкторов!
      public xPoints(int x, int y)
      {
          this.x = x;
          this.y = y;
      }
      
    // Вычисляется расстояние от начала координат.
     public int R
    {
         get
        {
             return (int)(Math.Sqrt(x * x + y * y));
         }
     }

    // После реализации соответствующего интерфейса объект-КОМПАРЕР
    // обеспечивает реализацию алгоритма сравнения.

    public int CompareTo(object p)
     {
         return (this.R - ((xPoints)p).R);
     }
 }

class yPoints
 {
     // Объект-генератор "случайных" чисел. 
    static Random rnd = new Random();

    public int x;
     public int y;

    public yPoints()
     {
         x = rnd.Next(0, 100);
         y = rnd.Next(0, 100);
     }

    // Шаблон функции в рамках объявления "обычного" класса.
    // Функция предназначена для формирования шаблонных магазинов
    // из входных массивов объектов - представителей подстановочного
    // класса, представленного параметром шаблона T. 
    public void  StackFormer<T>(Stack<T> stack, params T[] values)
     {
         foreach (T t in values)
         {
             stack.Push(t);
         }
     }
 }

 //==============================================================
 class Class1
 {
     static void Main(string[] args)
     {

        W<xPoints> xw0 = new W<xPoints>();
         W<xPoints> xw1 = new W<xPoints>();

 // Объекты - представители шаблонного класса можно сравнивать 
 // в результате
  // реализации интерфейса IComparable. 
        if (xw0.wCompare(xw1.t) == 0) Console.WriteLine("Yes");
         else                       Console.WriteLine("No");

 // В силу ограничений параметра шаблона T, следующий код 
 // для подстановочного
 // класса в принципе нереализуем.=============================

    //W<yPoints> yw0 = new W<yPoints>();
    //W<yPoints> yw1 = new W<yPoints>();
         
    //if (yw0.ww(yw1.t) == 0) Console.WriteLine("Yes");
    //else                    Console.WriteLine("No");
         //=====================================================

    // Демонстрация использования шаблона функции.
    // На основе подстановочного класса сформировали
    // шаблонную функцию для подстановочного класса,
    // которая обслуживает шаблонные очереди, формируемые на основе
    // предопределенного шаблона класса Queue<...>.
     Queue<xPoints> xpQueue = new Queue<xPoints>();
     xw1.QueueFormer<xPoints>(xpQueue,
                                      new xPoints(0, 9),
                                      new xPoints(1, 8),
                                      new xPoints(2, 7),
                                      new xPoints(3, 6),
                                      new xPoints(4, 5),
                                      new xPoints(5, 4),
                                      new xPoints(6, 3),
                                      new xPoints(7, 2),
                                      new xPoints(8, 1),
                                      new xPoints(9, 0)
                              );

    // Шаблоны классов и шаблоны функций концептуально не связаны.
    // В C# это самостоятельные и независимые конструкции.
    // Шаблон функции может быть объявлен где угодно -
    // в шаблоне класса и в рамках объявления "обычного" класса.
    // При объявлении шаблона функции ограничения на свойства 
    // подстановочного класса, представленного параметром шаблона, 
    // не упоминаются. 
    // В силу ограничений на параметр шаблона класс yPoints в принципе 
    // не может быть использован для построения шаблонного класса
    // на основе шаблона class W<T>.
    // Однако этот же самый класс может быть использован для построения
    // шаблонной функции в шаблонном классе, созданном на основе
    // подстановочного класса xPoints!  
    Queue<yPoints> ypQueue = new Queue<yPoints>();
     xw1.QueueFormer<yPoints>(ypQueue,
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints(),
                                      new yPoints()
                              );

 // А вот применение шаблона функции, объявленного в "обычном" классе.
 // Создали объект класса, содержащего шаблон функции
 // по обслуживанию очередей. 
yPoints yp = new yPoints();

 // Ссоздали шаблонный стек и воспользовались шаблоном функции, 
 // объявленной yPoints в классе.
 Stack<xPoints> xpStack = new Stack<xPoints>();
 yp.StackFormer<xPoints>(xpStack,
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints(),
                                   new xPoints()            
                                 );


    }
 }
 }
Листинг 13.2.

Пример использования шаблонов: сортировка

Старая задача, новые решения с использованием предопределенных шаблонов классов и интерфейсов...

using System;
using System.Collections;
using System.Collections.Generic;

namespace PatternArrays
{

// Данные для массива элементов.
// Подлежат сортировке в составе шаблонного массива методом Sort.

class Points
{
public int x;
public int y;

public Points(int key1, int key2)
{
     x = key1;
     y = key2;
 }

 // Вычисляется расстояние от начала координат.
 public int R
 {
     get
    {
         return (int)(Math.Sqrt(x * x + y * y));
     }
 }
 }


 // ...ШАБЛОННЫЙ КОМПАРЕР на основе шаблона интерфейса...   


class myComparer : IComparer<Points>
 {
 // Предлагаемый ШАБЛОННЫЙ метод сравнения возвращает разность расстояний
 // двух точек (вычисляется по теореме Пифагора) от начала координат
 // – точки с координатами (0,0). Чем ближе точки к началу координат
 // – тем они меньше. Не требуется никаких явных приведений типа.
 // Шаблон настроен на работу с классом Points.


int IComparer<Points>.Compare(Points p1, Points p2)
 {
     return (p1.R - p2.R);
 }
 }


 // После реализации соответствующего ШАБЛОННОГО интерфейса 
 // объект-КОМПАРЕР обеспечивает реализацию стандартного алгоритма 
 // сортировки.


class Class1
 {
 static void Main(string[] args)
 {
     // Объект-генератор "случайных" чисел. 
    Random rnd = new Random();

    int i;
     // Очередь Points.
     Queue<Points> qP = new Queue<Points>();
     // Шаблонный перечислитель. Предназначен для обслуживания
     // шаблонной очереди элементов класса Points.
     IEnumerator<Points> enP;
     // Сортировка поддерживается классом Array.
     Points[] pp;

    // Создали Компарер, способный сравнивать пары
     // объектов - представителей класса Points.  
    myComparer c = new myComparer();

    Console.WriteLine("========================================");

    // Проинициализировали массив объектов - представителей класса Points. 
    for (i = 0; i < 10; i++)
     {
         qP.Enqueue(new Points(rnd.Next(0, 10), rnd.Next(0, 10)));
     }

    enP = ((IEnumerable<Points>)(qP)).GetEnumerator();
     for (i = 0; enP.MoveNext(); i++)
     {
         Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y);
     }

    // Сортируются элементы массива типа Points, который формируется на 
    // основе шаблонной очереди.
    // Условием успешной сортировки элементов массива является 
    // реализация интерфейса IComparer. Если Компарер не сумеет 
    // справиться с поставленной задачей – будет возбуждено исключение.
    // На основе очереди построили массив. 

    pp = qP.ToArray();

    // А саму очередь можно почистить!
     qP.Clear();

    try
    {
         Array.Sort<Points>(pp, c);
     }
     catch (Exception ex)
     {
         Console.WriteLine(ex);
     }
    // Сортировка произведена, очередь восстановлена.
     for (i = 0; i < 10; i++) qP.Enqueue(pp[i]);

    Console.WriteLine("========================================");

    enP = ((IEnumerable<Points>)(qP)).GetEnumerator();
     for (i = 0; enP.MoveNext(); i++)
     {
         Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y);
     }

    Console.WriteLine("========================================");
 }
 }
 }
Листинг 13.3.

Nullable-типы

Nullable-типы (простые Nullable-типы) представляют собой расширения простых типов. Их объявления принадлежат пространству имен System.Nullable.

Это шаблонные типы, то есть типы, построенные в результате детализации шаблонов. Шаблон Nullable<> используется для расширения простых типов, которые по своей сути являются структурами. Для обозначения Nullable шаблонных (построенных на основе шаблона) типов используются две нотации:

Nullable<Int32> val; 	// Полная нотация.
 Nullable<int> val; 	// Полная нотация. Еще один вариант.

Int32? val;        	// Сокращенная нотация.
 int?   val;       	// Сокращенная нотация. Еще один вариант.

Шаблон расширяет диапазон возможностей шаблонных типов.

Такие типы имеют "обычный" для своих типов-аналогов диапазон значений, который дополняется null-значением. Кроме того, шаблонный тип получает дополнительные функциональные возможности в виде методов и свойств, что объясняется необходимостью работы с null значением.

Например, переменной типа Nullable<Int32> (следует говорить "Nullable of Int32") может быть присвоено значение в диапазоне от –2147483648 до 2147483647. Этой переменной также может быть присвоено значение null.

У Nullable-типов к тому же шире диапазон функциональных возможностей.

Переменной типа Nullable<bool> могут быть присвоены значения true, false, null.

Возможность присвоения такого значения переменной арифметического или Boolean типа может оказаться полезным при работе с базами данных и разработке типов (классов или структур), содержащих поля, которым может быть НЕ присвоено НИКАКОГО значения.

Пример объявления и применения nullable-значений приводится ниже:

class NullableExample
 {
     static void Main()
     {
 // Вопросительный знак в объявлении - признак nullab'ости переменной.
         int? num = null;
 // Здесь мы обращаемся к свойству, которое проверяет наличие "непустого"
 // значения у переменной num.
         if (num.HasValue == true)
         {
             System.Console.WriteLine("num = " + num.Value);
         }
         else
        {
             System.Console.WriteLine("num = Null");
         }

 // Простой двойник (типа int) - переменная y устанавливается в 0.
 // И это безопасный способ инициализации "простого" двойника. 
       
 int y = num.GetValueOrDefault();

 // Можно, конечно, попытаться присвоить значение "напрямую",
 // однако при этом сохраняется опасность того, что nullable в
 // данный момент (num.Value - свойство, позволяющее получить 
 // значение num) проинициализировано пустым значением. Тип int этого 
 // не вынесет - будет возбуждено исключение. Поэтому присвоение 
 // значения и сопровождается такими предосторожностями. 
        try
        {
             y = num.Value;
         }
         catch (System.InvalidOperationException e)
         {
             System.Console.WriteLine(e.Message);
         }
     }
 }
Листинг 13.4.

Результат выполнения программки:

num = Null
Nullable object must have a value.
kewezok kewezok
kewezok kewezok
Елена Шляхт
Елена Шляхт
Объясните плиз в чем отличие а++ от ++а
Почему результат разный?
int a=0, b=0;
Console.WriteLine(a++); //0
Console.WriteLine(++b); //1
a++;
++b;
Console.WriteLine(a); //2
Console.WriteLine(b); //2