C#. Hierarchies of generic classes

Hierarchies of generic classes. Generalized base and derived classes

In this topic, you will learn how to properly organize generic class hierarchies using inheritance technology.


Contents


Search other resources:




1. Features of creating hierarchies of generic classes. Base generic class. Requirements for derived classes

Generic classes support an inheritance mechanism and can form hierarchies. Any generic class that takes a type T as a parameter can be inherited by another derived class. In this case, a parameter of type T is passed to the derived class. This rule also applies to cases when the base class operates with several types T1, T2, …, TN. Thus, a derived class that inherits from a generic base class is also declared as a generic.

When you declare a derived class, a number of requirements are imposed on it.

Requirement 1. The derived class must receive the type (types) of the base class as a parameter. If there are several types, then all these types must be declared in the inherited class (see examples below). If this is not done, the compiler will generate an error like “The type or namespace name ‘T’ could not be found …”. In other words, the derived class must declare all types arguments that the base class needs.

It is allowed to declare your own (additional) type parameters in an inherited class (see example below). In this case, these parameters are specified together with the parameters of the base class, separated by commas. The order of the type parameters does not matter.

Requirement 2. If the base class has at least one constructor with parameters and this constructor receives a parameter of the generic type T, then the derived class must also implement a constructor in which the constructor of the base class is invoked. The base class constructor is called using the base keyword (see example below). Otherwise, a compilation error will occur.

If the derived class has more than one constructor, then this requirement applies to all of those constructors.

In the context of the above, it is important to understand the following: to create an object of a derived class, you must provide the base class with the necessary type argument up the hierarchy. The examples below show the implementation of this rule.

 

2. Features of inheritance of generic classes that operate with several generic types T1, T2, …, TN. Declaration syntax

A simplified syntax for declaring a base and derived class that takes multiple type parameters is as follows

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

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

here

  • Base – the name of the base class that receives as parameters the types T1, T2, …, TN;
  • Derived – the name of the class inherited from Base.

After that, you can create an instance of the derived class in the usual way.

...

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

...

here

  • type1, type2, …, typeN – placeholder types that the compiler uses to create obj from the generic Derived class.

 

3. Inheritance syntax for generic classes that operate on generic type T. Case: one base and one derived class

The syntax for forming a hierarchy from two generic classes, in which the base class Base receives the type T as a parameter, is as follows

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

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

Instantiation of derived class Derived<T> could be as follows

...

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

...

here

  • placeholder_type – a placeholder type on the basis of which a specific obj instance of the Derived class is created. A placeholder type can be a base type or any other type (class, structure) of the program.

 

4. A base and derived class that take the type T as a parameter. The base class contains a constructor. Example

The example deals with the case where the base class has a constructor with one parameter. This constructor must be called in an inherited class. Following a similar example, you can create your own class hierarchies for the generic type T.

using System;

namespace ConsoleApp19
{
  // Generic base class
  class A<T>
  {
    // Internal variable
    T objA;

    // Constructor
    public A(T _objA)
    {
      objA = _objA;
    }

    // Access property
    public T Value
    {
      get { return objA; }
      set { objA = value; }
    }
  }

  // Derived class
  class B<T> : A<T>
  {
    // Internal variable of class B<T>
    T objB;

    // There must be a constructor, if there is no constructor,
    // then a compilation error will occur
    public B(T _objA, T _objB) :
      base(_objA) // pass the _obj argument to the base class - required
    {
      objB = _objB;
    }

    // Property to access objB
    public new T Value
    {
      get { return objB; }
      set { objB = value; }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Declare an instance of the derived class
      B<int> obj1 = new B<int>(3, 8);

      // Display the value of instance
      Console.WriteLine("obj1.Value = {0}", obj1.Value); // obj1.Value = 8

      Console.ReadKey();
    }
  }
}

 

5. Declaring your own type parameters in an inherited class. Example

An inherited generic class can declare its own type parameters. This means that if a base class A<T> is given with a parameter of type T

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

then a derived class B can introduce its own type, for example, type V.

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

But, at the same time, in the derived class B, you need to declare a constructor that will receive two types of parameters T and V. Otherwise, you will not be able to create the instance of class B.

Example.

The example declares a base class Array<T> that implements an array of the generic type T. The Array2<T> class inherits from the Array<T> class, which adds a new array of a different type V. The main() function demonstrates the use of the derived class Array2<T>.

namespace ConsoleApp19
{
  // Generic base class - array
  class Array<T>
  {
    // Internal date - array
    T[] A;

    // Constructor
    public Array(T[] _A)
    {
      A = _A;
    }

    // Access methods.
    // Return an element of the array by its index
    public T GetItem(int index)
    {
      return A[index];
    }

    // return the reference to array
    public T[] GetArray() { return A; }

    // Method that outputs the elements of the array
    public void Print(string msg)
    {
      Console.WriteLine(msg);
      for (int i = 0; i < A.Length; i++)
        Console.Write("{0} ", A[i]);
      Console.WriteLine();
    }
  }

  // Derived class - extends the Array <T> class,
  // add another array of type V.
  class Array2<V, T> : Array<T>
  {
    V[] K;

    // Constructor - calls the constructor of the base class
    public Array2(V[] _K, T[] _A) : base(_A)
    {
      K = _K;
    }

    // Access methods.
    // Access to an element of the derived class.
    public V GetKey(int index)
    {
      return K[index];
    }

    // Access to the entire array
    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)
    {
      // Create two arrays of the same length
      char[] C = { 'z', ';', 'd' };
      double[] D = { 2.8, 3.5, 4.9 };

      // Create the instance of HashTable
      Array2<char, double> obj1 = new Array2<char, double>(C, D);

      // Print the value of the arrays of the obj1 instance
      obj1.Print("Double Array:");
      obj1.Print2("Char Array:");

      Console.ReadKey();
    }
  }
}

Program result

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

 

6. An example of declaring an inherited class from a generic base class that receives three types of parameters T1, T2, T3 and adds its own parameter of type T4

The example declares the base class Base, which receives as parameters the types T1, T2, T3. From a base class is inherited class Derived, which adds its own parameter of type T4. Following a similar example, you can implement your own generic class hierarchies.

using System;

namespace ConsoleApp1
{
  // Generic base class
  class Base<T1, T2, T3>
  {
    // Data of different types
    T1 value1;
    T2 value2;
    T3 value3;

    // Constructor
    public Base(T1 value1, T2 value2, T3 value3)
    {
      this.value1 = value1;
      this.value2 = value2;
      this.value3 = value3;
    }

    // Access methods
    public T1 GetValue1() { return value1; }
    public T2 GetValue2() { return value2; }
    public T3 GetValue3() { return value3; }
  }

  // Derived class, adds it's own type T4
  class Derived<T1, T2, T3, T4> : Base<T1, T2, T3>
  {
    // Own variable of type T4
    T4 value;

    // In the derived class, it is imperative to provide
    // filling in the fields of the base class.
    // This is done in the constructor
    public Derived(T1 value1, T2 value2, T3 value3, T4 value4) :
      base(value1, value2, value3) // pass data to base class constructor
    {
      value = value4;
    }

    // Method to access to value
    public T4 GetValue() { return value; }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Declare an instance of a derived class Derived
      Derived<int, float, char, bool> obj1 = new Derived<int, float, char, bool>(23, 2.5f, 'z', true);

      // Print data
      Console.WriteLine(obj1.GetValue1());
      Console.WriteLine(obj1.GetValue2());
      Console.WriteLine(obj1.GetValue3());
      Console.WriteLine(obj1.GetValue());

      Console.ReadKey();
    }
  }
}

The result of the program

23
2.5
z
True

 


Related topics