C#. Ограничения на конструктор, базовый класс и интерфейс

Ограничения на конструктор, базовый класс и интерфейс


Содержание


Поиск на других ресурсах:




1. Ограничение на конструктор: where T : new(). Особенности использования

Ограничение типа new() позволяет получить экземпляр (объект) обобщенного типа T в общем классе. Для того, чтобы получить этот экземпляр, нужно чтобы тип (класс) T обязательно содержал конструктор без параметров (конструктор по умолчанию).
Как известно, конструктор без параметров в классе может быть в одном из двух случаев:

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

Возможна ситуация, когда в классе реализованы один или несколько конструкторов с параметрами а конструктора без параметров не объявлено. В этом случае такой класс (тип) не подойдет для создания экземпляра (объекта) типа T.

 

2. Пример, объясняющий применение ограничения new()

Пусть заданы два класса:

  • класс MyClass, содержащий явно заданный конструктор без параметров;
  • клас GenClass<T>, устанавливающий ограничение new() для типа T.

Сокращенный программный код классов следующий:

// Класс, который содержит явно заданный конструктор без параметров
class MyClass
{
  // явно заданный конструктор без параметров
  public MyClass()
  {
    // ...
  }
}

// Обобщенный класс, получающий параметром тип T.
// Тип T имеет ограничение, которое требует, чтобы тип T содержал
// в своей реализации конструктор без параметров
class GenClass<T> where T : new()
{
  // Внутренняя переменная типа T
  T obj;

  // Конструктор класса GenClass<T>
  public GenClass()
  {
    // Создать экземпляр типа T
    obj = new T(); // работает, поскольку указано new()
  }
}

Как видно из вышеприведенного кода, в обобщённом классе создается экземпляр обобщенного типа T. Соответственно экземпляр обобщенного класса создается стандартным способом без проблем

...
// Создать экземпляр обобщенного класса
GenClass<MyClass> obj = new GenClass<MyClass>();
...

Если из класса MyClass убрать конструктор без параметров, а вместо него создать другой конструктор с параметрами, то при создании экземпляра класса GenClass<T> компилятор выдаст ошибку

// Класс, который не содержит конструктор без параметров
class MyClass
{
  // Некоторый конструктор з параметрами,
  // конструктора без параметров нету
  public MyClass(int a)
  {
    // ...
  }
}

// Обобщенный класс, получающий параметром тип T.
class GenClass<T> where T : new()
{
  // ...
}

...

// Создать экземпляр обобщенного класса
GenClass<MyClass> obj = new GenClass<MyClass>(); // здесь ошибка компиляции

...

Если из объявления класса GenClass<T> убрать ограничение where T: new() как показано ниже

// Класс не устанавливает ограничение new() на тип T
class GenClass<T>
{
  // Внутренняя переменная типа T
  T obj;

  // Конструктор класса GenClass<T>
  public GenClass()
  {
    // Создать экземпляр типа T невозможно!
    obj = new T(); // здесь не работает, ошибка на этапе компиляции
  }
}

то в конструкторе класса GenClass<T> создать экземпляр типа T будет невозможно.

 

3. Ограничение на базовый класс: where T : BaseClass

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

Ограничение на базовый класс дает следующие преимущества:

  • в обобщенном классе используется ограниченное количество членов, определенных в базовом классе и классами, производными от него. Все остальные классы и их элементы не используются («отфильтровываются»). Теперь компилятор знает о типах членов, которые может иметь аргумент типа;
  • обеспечивается гарантированное использование необходимого множества классов в качестве аргумента типа. Это множество классов ограничивается именем базового класса. Если при создании экземляров обобщенного класса попробовать указать тип, который не входит в нужное множество классов, то возникнет ошибка на этапе компиляции. Такой подход корректен и позволяет избежать трудноуловимых ошибок, которые могут возникнуть на этапе выполнения программы.

 

4. Пример, объясняющий ограничение на базовый класс. Рисунок

Пусть задано программный код классов с именами A, B, C, D, E, F, G, H, I а также программный код обобщенного класса MyClass<T> в котором тип T ограничивается базовым классом D

// Перечень классов, образующих иерархию
class A
{
  // ...
}

class B : A
{
  // ...
}

class C : A
{
  // ...
}

class D : A
{
  // ...
}

class E : D
{
  // ...
}

class F : D
{
  // ...
}

class G : F
{
  // ...
}

class H : B
{
  // ...
}

class I : B
{
  // ...
}

// Обобщенный класс, получающий параметром тип T, который ограничивается классом D
class MyClass<T> where T : D
{
  // ...
}

Согласно вышеприведенному коду, классы A, B, C, D, E, F, G, H, I образуют иерархию как показано на рисунке 1.

C#. Обобщения. Ограничение на базовый класс

Рисунок 1. Иерархия классов A, B, C, D, E, F, G, H, I. Ограничение на базовый класс D

Согласно рисунку 1, при объявлении экземпляра класса MyClass<T> допускается использовать типы (классы) D, E, F, G в качестве аргумента типа T

// Объявить экземпляр класса MyClass<T> в котором
// в качестве аргумента выступает тип D
MyClass<D> obj1 = new MyClass<D>(); // Ok!

// Аргументом выступает класс E, который есть подклассом класса D
MyClass<E> obj2 = new MyClass<E>(); // Ok!

// Аргументом выступает класс F, который есть подклассом класса D
MyClass<F> obj3 = new MyClass<F>(); // Ok!

// Подкласс класса F также может быть аргументом типа T класса MyClass
MyClass<G> obj4 = new MyClass<G>(); // Ok!

При попытке объявить экземпляр класса MyClass<T> с любым другим типом аргумента возникнет ошибка на этапе компиляции

// Попытка использовать другой класс, который не входит в иерархию
// где базовым классом выступает класс D
MyClass<int> obj = new MyClass<int>(); // ошибка на этапе компиляции

// Класс A является базовым для класса D, однако он не входит в иерархию,
// в которой базовым классом служит класс D. Поэтому это также ошибка
MyClass<A> obj5 = new MyClass<A>(); // ошибка на этапе компиляции

// Использование в качестве аргумента типа класса из библиотеки .NET Framework
// также является ошибкой
MyClass<System.IO.TextWriter> obj6 = new MyClass<System.IO.TextWriter>(); // ошибка компиляции

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

 

5. Ограничение на интерфейс: where T : Interface

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

Ограничения на интерфейс применяется в следующих случаях:

  • когда нужно использовать члены интерфейса в общем классе;
  • когда нужно гарантировать использование только тех аргументов типа, которые реализуют указанный интерфейс.

Допускается после ключевого слова where указывать перечень нескольких интерфейсов. Общая форма такого объявления примерно следующая

class ClassName<T> where T : Interface1, Interface2, ... InterfaceN
{
  ...
}

здесь

  • Interface1, Interface2, InterfaceN – перечень имен интерфейсов, которые должен реализовать тип T.

В случае с несколькими интерфейсами тип T должен содержать реализацию методов всех интерфейсов. Если не будет полностью реализован хотя бы один интерфейс, компилятор будет выдавать ошибку.

 

6. Пример, объясняющий ограничение на интерфейс

Пусть заданы следующие элементы:

  • интерфейс IInterface, в котором объявляется один метод Print();
  • класс InformClass, который реализует интерфейс IInterface;
  • класс MyClass, который не реализует интерфейс IInterface;
  • обобщенный (generic) класс GenClass<T> в котором тип T ограничивается именем интерфейса IInterface.

Ниже приводится сокращенный программный код вышеописанных элементов

...

// Ограничение на интерфейс
// 1. Имеется интерфейс с некоторым методом
interface IInformation
{
  // Некоторый метод в интерфейсе, метод выводит сообщение
  void PrintInfo(string message);
}

// 2. Класс, реализующий интерфейс IInfomation
class InformClass : IInformation
{
  // Реализация метода PrintInfo()
  public void PrintInfo(string message)
  {
    Console.WriteLine("InformClass: " + message);
  }
}

// 3. Класс, который НЕ РЕАЛИЗУЕТ интерфейс IInformation
class MyClass
{
  // Этот класс также содержит собственный метод PrintInfo()
  public void PrintInfo(string message)
  {
    Console.WriteLine("MyClass: " + message);
  }
}

// 4. Обобщенный класс, который содержит ограничение на интерфейс IInformation
class GenClass<T> where T : IInformation
{
  // ...
}

...

Если попытаться объявить экземпляр (объект) класса GenClass<T>, в котором аргументом типа T выступает класс InformClass

// 1. Попытка объявления экземпляра обобщенного класса,
// в котором параметром типа выступает класс InformClass
GenClass<InformClass> obj1 = new GenClass<InformClass>(); // работает

то такой код будет работать правильно. Это связано с тем, что класс InformClass реализует интерфейс IInformation

// 2. Класс, который реализует интерфейс IInfomation
class InformClass : IInformation
{
  ...
}

Если попытаться объявить экземпляр класса GenClass<T>, в котором аргументом типа выступает тип MyClass, то возникнет ошибка на этапе компиляции

// 2. Попытка использовать параметром типа класс MyClass
GenClass<MyClass> obj2 = new GenClass<MyClass>(); // ошибка компиляции

Это объясняется тем, что класс MyClass не реализует интерфейс IInformation

// Класс не реализует интерфейс IInformation
class MyClass
{
  ...
}

В этом случае срабатывает ограничение

where T : IInformation

в объявлении класса GenClass<T>.

 

7. Использование ограничений для установления связи между двумя параметрами типа. Неприкрытое ограничения типа в ограничениях where T: BaseClass. Пример

В обобщенных классах при использовании ограничений на базовый класс

where T : BaseClass

можно устанавливать связь между двумя параметрами типа.

В наиболее общем случае объявление такого обобщенного класса следующее

class MyClass<T1, T2> where T1:T2
{
  // ...
}

При таком объявлении указывается, что:

  • обобщенный класс MyClass получает параметрами два типа T1, T2;
  • тип T1 ограничен типом T2.

Ограничение на параметр типа T1 называется неприкрытым ограничением типа.

Пример. Пусть заданы следующие классы:

  • класс A, который есть базовым для классов B, C;
  • классы B, C, которые унаследованы от класса A;
  • обобщенный класс GenClass<T1, T2>, использующий ограничение типа T1 типом T2.

Программный код сокращенной реализации вышеуказанных классов следующий

// Некоторая иерархия классов
class A
{
  // ...
}

class B : A
{
  // ...
}

class C : A
{
  // ...
}

// Использование ограничений для установления связи между двумя параметрами типа.
// В классе GenClass тип T1 ограничивается типом T2.
class GenClass<T1, T2> where T1 : T2
{
  // ...
}

Теперь можно создавать экземпляр класса MyClass<T1, T2> следующим образом

// Создание экземпляра класса: GenClass<T1, T2> where T1:T2
GenClass<B, A> obj = new GenClass<B, A>(); // Ok!

Вышеприведенный фрагмент является правильным, поскольку класс B является подклассом класса A.

Если попытаться создать экземпляр класса другим способом

// здесь ошибка, класс A не есть подклассом класса B
GenClass<A, B> obj = new GenClass<A, B>(); // ошибка компиляции

// здесь также ошибка, класс C не есть подклассом класса B
GenClass<C, B> obj2 = new GenClass<C, B>(); // ошибка компиляции

// и это ошибка, класс B не есть подклассом класса C
GenClass<B, C> obj3 = new GenClass<B, C>(); // ошибка

то возникнет ошибка на этапе компиляции.

 


Связанные темы