Шаг 263.
Язык программирования C#. Начала.
Обобщенные типы. Ограничения на параметры типа

    На этом шаге мы рассмотрим задание таких ограничений.

    В некоторых случаях при описании обобщенных методов, классов, структур, интерфейсов или делегатов необходимо наложить ограничения на значения для обобщенных параметров. Например, ограничение может быть таким: значением обобщенного параметра должно быть только имя класса. Или наоборот, имя класса не может быть значением обобщенного параметра. Или значением обобщенного параметра должно быть имя класса, являющегося производным от какого-то определенного класса, и так далее. Все это можно реализовать, причем достаточно легко.

    Если обобщенный параметр не может принимать произвольное значение, а должен соответствовать определенным критериям, то в описании соответствующего обобщенного класса, метода, структуры, интерфейса или делегата указывается специальная инструкция, которая начинается с ключевого слова where. После этого ключевого слова следует название параметра (для которого определяется ограничение) и через двоеточие некоторое выражение. Это выражение, собственно, и определяет ограничение, накладываемое на обобщенный параметр. Наиболее характерные случаи представлены в таблице 1 (далее через T обозначен обобщенный параметр).

Таблица 1. Ограничения на обобщенные параметры
Выражение Значение
where T: class Значением обобщенного параметра T может быть имя класса
where T: struct Значением обобщенного типа T может быть только нессылочный тип (базовый тип или структура)
where T: new() Значением обобщенного параметра T может быть тип, для которого предусмотрен конструктор без аргументов. Если на обобщенный параметр накладывается несколько ограничений, то данное ограничение должно быть последним
where T: базовый_класс Значением обобщенного параметра T может быть имя класса, который является производным для указанного после двоеточия базового класса (обозначен как базовый_класс)
where T: интерфейс Значением обобщенного параметра T может быть имя класса, реализующего данный интерфейс

    Как все это выглядит на практике, иллюстрируют примеры этого и последующих шагов. В примере ниже представлена программа, в которой описан обобщенный класс с одним параметром. На этот параметр накладывается ограничение: его значением может быть только класс, производный от одного из обычных классов, также описанных в программе.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr263_1
{
    // Базовый класс: 
    class Alpha {
        // Поле:
        protected int code;
        // Конструктор: 
        public Alpha(int n){ 
            code = n;
        }
        // Виртуальный метод: 
        public virtual void show() {
            Console.WriteLine("Kлacc Alpha: " + code); 
        }
    }

    // Производный класс: 
    class Bravo: Alpha {
        // Конструктор:
        public Bravo(int n): base(n){}
        // Переопределение метода: 
        public override void show() {
            Console.WriteLine("Kлacc Bravo: " + code);
        }

    }

    // Производный класс: 
    class Charlie: Bravo {
        // Конструктор:
        public Charlie(int n): base(n){}
        // Переопределение метода: 
        public override void show() {
            Console.WriteLine("Kлacc Charlie: " + code);
        }
    }

    // Обобщенный класс с ограничением на обобщенный параметр: 
    class MyClass <T> where T: Alpha {
        // Поле - ссылка на объект: 
        public Alpha obj;
        // Конструктор обобщенного класса: 
        public MyClass(T t) {
            // Ссылка на объект: 
            obj = t;
            // Вызов метода: 
            obj.show();
        }
    }

    // Главный класс: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Создание объектов обычных классов:
            Alpha A = new Alpha(100);
            Bravo B = new Bravo(200);
            Charlie C = new Charlie(300);
            // Создание объектов на основе обобщенного класса:
            MyClass<Alpha> objA = new MyClass<Alpha>(A);
            MyClass<Bravo> objB = new MyClass<Bravo>(B);
            MyClass<Charlie> objC = new MyClass<Charlie>(C);
            MyClass<Alpha> objD = new MyClass<Alpha>(C);
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы следующий:


Рис.1. Результат работы приложения

    Во-первых, мы описали три обычных класса Alpha, Bravo и Charlie. Класс Alpha является базовым для класса Bravo, а класс Bravo является базовым для класса Charlie. В классе Alpha описано защищенное целочисленное поле code, конструктор с одним аргументом и виртуальный метод show(), который при вызове отображает название класса и значение поля code. В производных классах Bravo и Charlie метод show() переопределяется так, чтобы отображалось имя соответствующего класса (нам это нужно для того, чтобы можно было определить, из объекта какого класса вызывается метод).

    Класс MyClass является обобщенным. У него один обобщенный параметр, обозначенный как T. Ограничение where T: Alpha, накладываемое на этот параметр, означает, что значением параметра T может быть имя класса, являющегося производным от класса Alpha (класс Alpha также попадает в данную категорию). В классе MyClass есть поле obj. Это ссылка на объект класса Alpha. Конструктору обобщенного класса передается один аргумент - ссылка на объект обобщенного типа T. Данная ссылка в качестве значения присваивается полю obj. Такая операция возможна, поскольку, как мы помним, значением обобщенного параметра T может быть не любой класс, а только производный класс от класса Alpha. Далее, объектная переменная базового класса может ссылаться на объект производного класса. Поэтому поле obj, являющееся объектной переменной класса Alpha, может ссылаться на объект класса, который "скрывается" за параметром T.

    В конструкторе обобщенного класса MyClass из объекта, на который ссылается поле obj, вызывается метод show() (команда

  obj.show(); 
). В итоге при создании объекта на основе обобщенного класса появляется сообщение, в котором указано имя обычного класса (Alpha, Bravo или Charlie). Это класс объекта, на который записана ссылка в поле obj. Также в сообщении отображается значение поля code этого объекта.

    В главном методе создается объект A класса Alpha со значением 100 для поля code, объект B класса Bravo со значением 200 для поля code и объект C класса Charlie со значением 300 для поля code. Далее на основе обобщенного класса MyClass создается четыре объекта objA, objB, objC и objD. Каждый раз при создании такого объекта появляется сообщение в консольном окне. Причем при создании последнего объекта objD значением обобщенного параметра указан класс Alpha, хотя фактически в качестве аргумента конструктору класса MyClass передается объект C класса Charlie. Но, как видим, эта ситуация обрабатывается корректно.

    На следующем шаге мы продолжим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг