C#. Иерархии обобщенных классов

Иерархии обобщенных классов. Обобщенный базовый и производный классы

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


Содержание


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




1. Особенности создания иерархий обобщенных классов. Базовый обобщенный класс. Требования к производным классам

Обобщенные классы поддерживают механизм наследования и могут образовывать иерархии. Любой обобщенный класс, получающий параметром некоторый тип T, может быть унаследован другим производным классом. При этом параметр типа T передается в производный класс. Это правило касается и случаев, когда базовый класс оперирует несколькими типами T1, T2, …, TN. Таким образом, производный класс, который наследует обобщенный базовый класс, также объявляется обобщенным.

При объявлении производного класса к нему предъявляется ряд требований.

Требование 1. Производный класс обязательно должен получать параметром тип (типы) базового класса. Если типов несколько, то все эти типы должны быть объявлены в унаследованном классе (см. Примеры ниже). Если этого не сделать, компилятор выдаст ошибку типа «The type or namespace name ‘T’ could not be found …». Иными словами, производный класс должен объявлять все аргументы типа (типов), которые необходимы базовому классу.

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

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

Если в производном классе есть несколько конструкторов, то это требование касается всех этих конструкторов.

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

 

2. Особенности наследования обобщенных классов, которые оперируют несколькими обобщенными типами T1, T2, …, TN. Синтаксис объявления

Упрощенный синтаксис объявления базового и производного классов, которые получают несколько параметров типов следующий

class Base<T1, T2, ..., TN>
{
  ...
}

class Derived<T1, T2, ..., TN> : Base<T1, T2, ..., TN>
{
  ...
}

здесь

  • Base — имя базового класса, который получает параметрами типы T1, T2, …, TN;
  • Derived — имя унаследованного от Base класса.

После этого можно создавать экземпляр производного класса обычным способом

...

Derived<type1, type2, ..., typeN> obj = new Derived<type1, type2, ..., typeN>(...);

...

здесь

  • type1, type2, …, typeN – типы-заполнители, которые компилятор использует для создания объекта obj из обобщенного класса Derived.

 

3. Синтаксис наследования обобщенных классов, которые оперируют обобщенным типом T. Случай: один базовый и один производный классы

Синтаксис образования иерархии из двух обобщенных классов, в которых базовый класс Base получает параметром тип T, следующий

class Base<T>
{
  // ...
}

class Derived<T> : Base<T>
{
  // ...
}

Создание экземпляра производного класса Derived<T> может быть следующим

...

Derived<type> obj = new Derived<type>(...)

...

здесь

  • type — тип-заполнитель (placeholder type), на основе которого создается конкретный экземпляр obj класса Derived. Типом-заполнителем может быть базовый тип или любой другой тип (класс, структура) программы.

 

4. Базовый и производный класс, которые получают параметром тип T. базовый класс содержит конструктор. Пример

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

using System;

namespace ConsoleApp19
{
  // Обобщенный базовый класс
  class A<T>
  {
    // Внутренняя переменная
    T objA;

    // Конструктор
    public A(T _objA)
    {
      objA = _objA;
    }

    // Свойство доступа
    public T Value
    {
      get { return objA; }
      set { objA = value; }
    }
  }

  // Производный класс
  class B<T> : A<T>
  {
    // Внутренняя переменная класса B<T>
    T objB;

    // Обязательно должен быть конструктор,
    // если конструктора нету, то возникнет ошибка компиляции
    public B(T _objA, T _objB) :
      base(_objA) // передать аргумент _obj базовому классу - обязательно
    {
      objB = _objB;
    }

    // Свойство доступа к objB
    public new T Value
    {
      get { return objB; }
      set { objB = value; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Объявить экземпляр производного класса
      B<int> obj1 = new B<int>(3, 8);

      // Вывести значение экземпляра
      Console.WriteLine("obj1.Value = {0}", obj1.Value); // obj1.Value = 8

      Console.ReadKey();
    }
  }
}

 

5. Объявление собственных параметров типа в унаследованном классе. Пример

Унаследованный обобщенный класс может объявлять собственные параметры типа. Это означает, что если задан базовый класс A<T> с параметром типа T

class A<T>
{
  // ...
}

то производный от него класс B может ввести собственный тип, например, тип V.

class B<T, V>
{
  // ...
}

Но, при этом в производном классе B нужно объявлять конструктор, который будет получать параметрами два типа T и V. Иначе, создать экземпляр класса B не удастся.

Пример.

В примере объявляется базовый класс Array<T>, реализующий массив обобщенного типа T. Из класса Array<T> наследуется класс Array2<T>, который добавляет новый массив другого типа V. В функции main() демонстрируется использование производного класса Array2<T >.

namespace ConsoleApp19
{
  // Обобщенный базовый класс - массив
  class Array<T>
  {
    // Внутренние данные - массив
    T[] A;

    // Конструктор
    public Array(T[] _A)
    {
      A = _A;
    }

    // Методы доступа.
    // Вернуть элемент массива по его индексу
    public T GetItem(int index)
    {
      return A[index];
    }

    // вернуть ссылку на массив
    public T[] GetArray() { return A; }

    // Метод, который выводит элементы массива
    public void Print(string msg)
    {
      Console.WriteLine(msg);
      for (int i = 0; i < A.Length; i++)
        Console.Write("{0} ", A[i]);
      Console.WriteLine();
    }
  }

  // Производный класс - расширяет класс Array<T>,
  // добавляет другой массив типа V.
  class Array2<V, T> : Array<T>
  {
    V[] K;

    // Конструктор - вызывает конструктор базового класса
    public Array2(V[] _K, T[] _A) : base(_A)
    {
      K = _K;
    }

    // Методы доступа.
    // Доступ к элементу производного класса.
    public V GetKey(int index)
    {
      return K[index];
    }

    // Доступ ко всему массиву
    public V[] GetKeys()
    {
      return K;
    }

    public void Print2(string msg)
    {
      Console.WriteLine(msg);
      for (int i = 0; i < K.Length; i++)
        Console.Write("{0} ", K[i]);
      Console.WriteLine();
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Создать два массива одинаковой длины
      char[] C = { 'z', ';', 'd' };
      double[] D = { 2.8, 3.5, 4.9 };

      // Создать экземпляр HashTable
      Array2<char, double> obj1 = new Array2<char, double>(C, D);

      // Вывести значение массивов экземпляра obj1
      obj1.Print("Double Array:");
      obj1.Print2("Char Array:");

      Console.ReadKey();
    }
  }
}

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

Double Array:
2.8 3.5 4.9
Char Array:
z ; d

 

6. Пример объявления унаследованного класса из базового обобщенного класса, который получает параметрам три типа T1, T2, T3 и добавляет собственный параметр типа T4

В примере объявляется базовый класс Base, который получает параметрами типы T1, T2, T3. Из базового класса наследуется класс Derived, который добавляет собственный параметр типа T4. По подобному примеру можно реализовывать собственные иерархии обобщенных классов.

using System;

namespace ConsoleApp1
{
  // Обобщенный базовый класс
  class Base<T1, T2, T3>
  {
    // Данные различных типов в классе
    T1 value1;
    T2 value2;
    T3 value3;

    // Конструктор
    public Base(T1 value1, T2 value2, T3 value3)
    {
      this.value1 = value1;
      this.value2 = value2;
      this.value3 = value3;
    }

    // Методы доступа
    public T1 GetValue1() { return value1; }
    public T2 GetValue2() { return value2; }
    public T3 GetValue3() { return value3; }
  }

  // Производный класс, добавляет собственный тип T4
  class Derived<T1, T2, T3, T4> : Base<T1, T2, T3>
  {
    // Собственная переменная типа T4
    T4 value;

    // В производном классе нужно обязательно обеспечить
    // заполнение полей базового класса.
    // Это делается в конструкторе
    public Derived(T1 value1, T2 value2, T3 value3, T4 value4) :
      base(value1, value2, value3) // передать данные в конструктор базового класса
    {
      value = value4;
    }

    // Метод доступа к value
    public T4 GetValue() { return value; }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Объявить экземпляр производного класса Derived
      Derived<int, float, char, bool> obj1 = new Derived<int, float, char, bool>(23, 2.5f, 'z', true);

      // Вывести данные
      Console.WriteLine(obj1.GetValue1());
      Console.WriteLine(obj1.GetValue2());
      Console.WriteLine(obj1.GetValue3());
      Console.WriteLine(obj1.GetValue());

      Console.ReadKey();
    }
  }
}

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

23
2.5
z
True

 


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