Generic Classes and Inheritance

Introduction

Consider the following geometric figures:

Square Rectangle Trapezoid Parallelogram
Square Rectangle Trapezoid Parallelogram

Notice that these are geometric figures with each having four sides. From what we know so far, we can create a base class to prepare it for inheritance. If the class is very general, we can make it a generic one. We can set a data type as an unknown type, anticipating that the dimensions of the figure can be considered as integer or double-precision types. Here is an example:

public class Quadrilateral<T>
{
    protected T _base;
    protected T _height;
    protected string? _name;

    public virtual T Base
    {
        get { return _base; }
        set { _base = value; }
    }

    public virtual T Height
    {
        get { return _height; }
        set { _height = value; }
    }

    public virtual string? Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Quadrilateral(string name = "Quadrilateral")
    {
        _name = name;
    }

    public Quadrilateral(T bs, T height)
    {
        _name = "Quadrilateral";
        _base = bs;
        _height = height;
    }

    public Quadrilateral(string name, T bs, T height)
    {
        _name = name;
        _base = bs;
        _height = height;
    }

    public virtual string Describe()
    {
        return "A quadrilateral is a geometric figure with four sides";
    }

    public virtual string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}, Base: {Base}, Height: {Height}";
    }
}

You can then use the class by declaring a variable of it. Here are examples:

using static System.Console;

Title = "Geometry: Quadrilaterals";
WriteLine("Geometry: Quadrilaterals");
WriteLine("--------------------------------");

// Trapezoid with equal sides
var kite = new Quadrilateral<double>("Beach Kite", 18.64, 18.64);

WriteLine(kite.ShowCharacteristics());
            
// Rectangle, in meters
var basketballStadium = new Quadrilateral<Byte>();

basketballStadium.Name = "Basketball Stadium";
basketballStadium.Base = 15;
basketballStadium.Height = 28;

WriteLine(basketballStadium.ShowCharacteristics());
WriteLine("=================================");

public class Quadrilateral<T>
{
    protected T _base;
    protected T _height;
    protected string? _name;

    public virtual T Base
    {
        get { return _base; }
        set { _base = value; }
    }

    public virtual T Height
    {
        get { return _height; }
        set { _height = value; }
    }

    public virtual string? Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Quadrilateral(string name = "Quadrilateral")
    {
        _name = name;
    }

    public Quadrilateral(T bs, T height)
    {
        _name = "Quadrilateral";
        _base = bs;
        _height = height;
    }

    public Quadrilateral(string name, T bs, T height)
    {
        _name = name;
        _base = bs;
        _height = height;
    }

    public virtual string Describe()
    {
        return "A quadrilateral is a geometric figure with four sides";
    }

    public virtual string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}, Base: {Base}, Height: {Height}";
    }
}

This would produce:

Geometry: Quadrilaterals
--------------------------------
Geometric Figure: Beach Kite, Description: A quadrilateral is a geometric figure
 with four sides, Base: 18.64, Height: 18.64
Geometric Figure: Basketball Stadium, Description: A quadrilateral is a geometri
c figure with four sides, Base: 15, Height: 28
=================================
Press any key to continue . . .

The Nullity of a Generic Parameter

We have seen that, when you are creating a generic class, the compiler doesn't know the type of the parameter you are using. This means that the generic parameter can be a reference type. This also means that the values or objects of the parameter could eventually hold null values. This would cause the compiler to issue a warning on a field or property created from the generic parameter. To prepare for this, you can apply the null-conditional operator on every field or property created from the parameter type. This can be done as follows:

public class Quadrilateral<T>
{
    protected T? _base;
    protected T? _height;
    protected string? _name;

    public virtual T? Base
    {
        get { return _base; }
        set { _base = value; }
    }

    public virtual T? Height
    {
        get { return _height; }
        set { _height = value; }
    }

    public virtual string? Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Quadrilateral(string name = "Quadrilateral")
    {
        _name = name;
    }

    public Quadrilateral(T bs, T height)
    {
        _name = "Quadrilateral";
        _base = bs;
        _height = height;
    }

    public Quadrilateral(string name, T bs, T height)
    {
        _name = name;
        _base = bs;
        _height = height;
    }

    public virtual string Describe()
    {
        return "A quadrilateral is a geometric figure with four sides";
    }

    public virtual string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}, Base: {Base}, Height: {Height}";
    }
}

Deriving from a Generic Class

If you have a generic class that can serve as a foundation for another class, you can derive a class from the generic one. To do this, use the formula we apply when deriving a class but follow the name of each class with <>. Inside the <> operator, enter the same identifier to indicate that the class is a generic type that is based on another generic class. Here is an example:

public class Square<T> : Quadrilateral<T>
{
}

In the body of the new class, you can use the parameter type. For example, you can declare some member variables of that type. You can create methods that return the parameter type or you can pass arguments of the parameter type. When implementing the methods of the new class, use the member variables of the parameter and the argument(s) based on the parameter type. You can then declare a variable of the class and use it as we have done so far for other generic classes. Here is an example:

public class Quadrilateral<T>
{
    protected T? _base;
    protected T? _height;
    protected string? _name;

    public virtual T? Base
    {
        get { return _base; }
        set { _base = value; }
    }

    public virtual T? Height
    {
        get { return _height; }
        set { _height = value; }
    }

    public virtual string? Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Quadrilateral(string name = "Quadrilateral")
    {
        _name = name;
    }

    public Quadrilateral(T bs, T height)
    {
        _name = "Quadrilateral";
        _base = bs;
        _height = height;
    }

    public Quadrilateral(string name, T bs, T height)
    {
        _name = name;
        _base = bs;
        _height = height;
    }

    public virtual string Describe()
    {
        return "A quadrilateral is a geometric figure with four sides";
    }

    public virtual string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}, Base: {Base}, Height: {Height}";
    }
}

public class Square<T> : Quadrilateral<T>
{
    public Square()
    {
        _name = "Square";
    }
    
    public Square(string name)
    {
        _name = "Square";
    }
    
    public Square(T side)
    {
        _name = "Square";
        _base = side;
        _height = side;
    }
    
    public Square(string name, T side)
    {
        _name = name;
        _base = side;
        _height = side;
    }
    
    public override string Describe()
    {
        return "A square is a quadrilateral with four equal sides";
    }
    
    public override string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}. {Describe()}, Side: {Base}";
    }
}

You can then use the class. Here is an example:

using static System.Console;

Title = "Geometry: Quadrilaterals";
WriteLine("Geometry: Quadrilaterals");
WriteLine("--------------------------------");

var plate = new Square<Byte>();

plate.Name = "Plate";
plate.Base = 15;
plate.Height = 28;

WriteLine(plate.ShowCharacteristics());
WriteLine("=================================");

public class Quadrilateral<T>
{
    protected T? _base;
    protected T? _height;
    protected string? _name;

    public virtual T? Base
    {
        get { return _base; }
        set { _base = value; }
    }

    public virtual T? Height
    {
        get { return _height; }
        set { _height = value; }
    }

    public virtual string? Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Quadrilateral(string name = "Quadrilateral") => _name = name;

    public Quadrilateral(T bs, T height)
    {
        _name = "Quadrilateral";
        _base = bs;
        _height = height;
    }

    public Quadrilateral(string name, T bs, T height)
    {
        _name = name;
        _base = bs;
        _height = height;
    }

    public virtual string Describe() => "A quadrilateral is a geometric figure with four sides";

    public virtual string ShowCharacteristics() => $"Geometric Figure: {Name}, Description: {Describe()}, Base: {Base}, Height: {Height}";
}

public class Square<T> : Quadrilateral<T>
{
    public Square()
    {
        _name = "Square";
    }
        
    public Square(string name)
    {
        _name = "Square";
    }
        
    public Square(T side)
    {
        _name = "Square";
        _base = side;
        _height = side;
    }
        
    public Square(string name, T side)
    {
        _name = name;
        _base = side;
        _height = side;
    }
        
    public override string Describe()
    {
        return "A square is a quadrilateral with four equal sides";
    }
        
    public override string ShowCharacteristics()
    {
        return $"Geometric Figure: {Name}, Description: {Describe()}. {Describe()}, Side: {Base}";
    }
}

This would produce:

Geometry: Quadrilaterals
--------------------------------
Geometric Figure: Plate, Description: A square is a quadrilateral with four equa
l sides. A square is a quadrilateral with four equal sides, Side: 15
=================================
Press any key to continue . . .

A Generic Interface

Introduction

If you are planning to create many generic classes, you can start or provide their common characteristics or behaviors in an interface.

Creating a Generic Interface

To create a generic interface, you primarily follow the rules for creating an interface except that you must add a parameter type. Here is an example:

public interface ICounter<T>
{
}

You should also add a (the) member(s) that the implementers will have to override. Here are examples of two members:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

In the same way, you can derive a generic interface from another generic interface. Here is an example:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

Implementing a Generic Interface

After creating a generic interface, when deriving a class from it, follow the formula we reviewed for inheriting from a generic class. This means that the deriving class must use (a) parameter type(s). Here is an example:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class People<T> : IPersons<T>
{
    
}

When implementing the derived class, you must observe all rules that apply to interface implementation. That is, you must implement all the members of the generic interface. Of course, you can also add new members if you want. Here is an example:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

	// This is a property implemented from the ICounter interface
    public int Count
    {
        get
        {
            return size;
        }
    }

    // This is a method implemented from the IBuilder interface
    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }

    // This is a method implemented from the ICounter interface
    public T Get(int index)
    {
        return objects[index];
    }
}

After implementing the interface, you can declare a variable of the class and use it as you see fit. To do this, after the nane of the class, make sure you specify the parameter type between < and >. Initialize the variable appropriately. After that, you can acccess the members of the class. Here is an example:

using static System.Console;

Title = "Social Anthropology";
WriteLine("Social Anthropology");
WriteLine("------------------------------------------");

Associate<string> anthropology = new Associate<string>();

anthropology.Add("Convention");
anthropology.Add("Economics");
anthropology.Add("Politics");
anthropology.Add("Social Conflict");
anthropology.Add("Consumption");

WriteLine("The study of social anthropology includes:");

for (int i = 0; i < anthropology.Count; i++)
    WriteLine(anthropology.Get(i));
    
WriteLine("==========================================");

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

    // This is a property implemented from the ICounter interface
    public int Count
    {
        get
        {
            return size;
        }
    }

    // This is a method implemented from the IBuilder interface
    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }
        
    // This is a method implemented from the ICounter interface
    public T Get(int index)
    {
        return objects[index];
    }
}

This would produce:

Social Anthropology
------------------------------------------
The study of social anthropology includes:
Convention
Economics
Politics
Social Conflict
Consumption
==========================================
Press any key to continue . . .

In the above example, we used a primitive type, namely a string, as a parameter type. Otherwise, you can use a class, either one of the many .NET Framework built-in classes or you can create your own.

Remember that you can declare the variable using either the var or the dynamic keyword. Here are examples:

using static System.Console;

var anthropology = new Associate<string>();
dynamic salaries = new Associate<decimal>();

anthropology.Add("Convention");
anthropology.Add("Economics");
anthropology.Add("Politics");
anthropology.Add("Social Conflict");
anthropology.Add("Consumption");
	
for(int i = 0; i < anthropology.Count; i++)
    anthropology.Get(i);

A Generic Interface as Parameter

A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. Here is an example:

using static System.Console;

Title = "Social Anthropology";
WriteLine("Social Anthropology");
WriteLine("------------------------------------------");

IBuilder<string> anthropology = new Associate<string>();

anthropology.Add("Convention");
anthropology.Add("Economics");
anthropology.Add("Politics");
anthropology.Add("Social Conflict");
anthropology.Add("Consumption");

WriteLine("The study of social anthropology includes:");

for (int i = 0; i < anthropology.Count; i++)
    WriteLine(anthropology.Get(i));
            
WriteLine("==========================================");

In the same way, a generic interface can be returned from a method. Here is an example:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

    public int Count { get { return size; } }

    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }

    public T Get(int index) { return objects[index]; }
}

public class College
{
    public IBuilder<Course> CreateCourses()
    {
        Associate<Course> courses = new Associate<Course>();

        Course crs = new Course();
        crs.CourseName = "Online Research";
        crs.Credits = 1;
        courses.Add(crs);
        crs = new Course() { CourseName = "General Chemistry", Credits = 3 };
        courses.Add(crs);
        courses.Add(new Course() { CourseName = "Workplace Learning in Biology", Credits = 6 });
        courses.Add(new Course() { CourseName = "Linear Geometry 1", Credits = 4 });

        return courses;
    }
}

public record Course
{
    public string? CourseName { get; set; }
    public int Credits        { get; set; }
}

Here is an example of getting the returned generic value:

using static System.Console;

Title = "Social Anthropology";
WriteLine("Social Anthropology");
WriteLine("------------------------------------------");

College university = new College();
IBuilder<Course> studies = university.CreateCourses();

for (int i = 0; i < studies.Count; i++)
    WriteLine("{0}: {1} Credits", studies.Get(i).CourseName, studies.Get(i).Credits);
            
WriteLine("==========================================");

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

    public int Count { get { return size; } }

    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }

    public T Get(int index) { return objects[index]; }
}

public class College
{
    public IBuilder<Course> CreateCourses()
    {
        Associate<Course> courses = new Associate<Course>();

        Course crs = new Course();
        crs.CourseName = "Online Research";
        crs.Credits = 1;
        courses.Add(crs);
        crs = new Course() { CourseName = "General Chemistry", Credits = 3 };
        courses.Add(crs);
        courses.Add(new Course() { CourseName = "Workplace Learning in Biology", Credits = 6 });
        courses.Add(new Course() { CourseName = "Linear Geometry 1", Credits = 4 });

        return courses;
    }
}

public record Course
{
    public string? CourseName { get; set; }
    public int Credits        { get; set; }
}

This would produce:

Social Anthropology
------------------------------------------
Online Research: 1 Credits
General Chemistry: 3 Credits
Workplace Learning in Biology: 6 Credits
Linear Geometry 1: 4 Credits
==========================================
Press any key to continue . . .

A generic interface can also be passed as argument. You pass a generic interface primarily the same way you would a regular interface. In the body of the method, you can ignore the argument or use it any way appropriate. Here is an example:

public class College
{
    public IBuilder<Course> CreateCourses()
    {
        Associate<Course> courses = new Associate<Course>();

        Course crs = new Course();
        crs.CourseName = "Online Research";
        crs.Credits = 1;
        courses.Add(crs);
        crs = new Course() { CourseName = "General Chemistry", Credits = 3 };
        courses.Add(crs);
        courses.Add(new Course() { CourseName = "Workplace Learning in Biology", Credits = 6 });
        courses.Add(new Course() { CourseName = "Linear Geometry 1", Credits = 4 });

        return courses;
    }

    public string Show(IBuilder<Course> values)
    {
        string strCourses = "";

        for (int i = 0; i <= 4; i++)
        {
            strCourses += values.Get(i).CourseName + "(" + values.Get(i).Credits + "), ";
        }

        strCourses = strCourses.Substring(0, strCourses.Length - 2);

        return strCourses;
    }
}

public record Course
{
    public string? CourseName { get; set; }
    public int Credits        { get; set; }
}

Here is an example of passing the generic object as argument:

using static System.Console;

Title = "Anthropology - College Courses";
WriteLine("Anthropology - College Courses");
WriteLine("---------------------------------------------------------------");
            
College university = new College();
IBuilder<Course> studies = new Associate<Course>();
            
studies.Add(new Course() { CourseName = "Business Startup", Credits = 1 });
studies.Add(new Course() { CourseName = "Introduction to Business and Management", Credits = 3 });
studies.Add(new Course() { CourseName = "Fundamentals of Digital Media", Credits = 3 });
studies.Add(new Course() { CourseName = "Predictive Modeling", Credits = 6 });
studies.Add(new Course() { CourseName = "Financial Management for Health Care Organizations", Credits = 3 });
            
string summary = university.Show(studies);

WriteLine(summary);

WriteLine("===============================================================");

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

    public int Count { get { return size; } }

    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }

    public T Get(int index) { return objects[index]; }
}

public class College
{
    public IBuilder<Course> CreateCourses()
    {
        Associate<Course> courses = new Associate<Course>();

        Course crs = new Course();
        crs.CourseName = "Online Research";
        crs.Credits = 1;
        courses.Add(crs);
        crs = new Course() { CourseName = "General Chemistry", Credits = 3 };
        courses.Add(crs);
        courses.Add(new Course() { CourseName = "Workplace Learning in Biology", Credits = 6 });
        courses.Add(new Course() { CourseName = "Linear Geometry 1", Credits = 4 });

        return courses;
    }

    public string Show(IBuilder<Course> values)
    {
        string strCourses = string.Empty;

        for (int i = 0; i <= 4; i++)
            strCourses += values.Get(i).CourseName + " (" + values.Get(i).Credits + " Credits)\n";

        strCourses = strCourses.Substring(0, strCourses.Length - 1);

        return strCourses;
    }
}

public record Course
{
    public string? CourseName { get; set; }
    public int    Credits     { get; set; }
}

This would produce:

Anthropology - College Courses
---------------------------------------------------------------
Business Startup (1 Credits)
Introduction to Business and Management (3 Credits)
Fundamentals of Digital Media (3 Credits)
Predictive Modeling (6 Credits)
Financial Management for Health Care Organizations (3 Credits)
===============================================================
Press any key to continue . . .

A Generic Interface as a Parameter Type

A generic interface can be used as a parameter type. When creating a method, in its <> operator, specify the desired interface. A generic interface can also be used as the parameter type of an interface. As the number one rule for all methods that return a value, before exiting the method, you must return an object that is compatible with the generic interface. To do this, in the body of the method, you can declare a variable of a class that implements the interface, use that variable any appropriate way you want, and return it. Here is an example:

public interface ICounter<T>
{
    int Count { get; }
    T Get(int index);
}

public interface IBuilder<T> : ICounter<T>
{
    void Add(T item);
}

public interface IGovernment
{
    string? Continent { get; set; }
    string? Name { get; set; }
}

public class Country : IGovernment
{
    public string? Continent { get; set; }
    public string? Name { get; set; }
}

public class Associate<T> : IBuilder<T>
{
    private int size;
    private T[] objects;

    public Associate()
    {
        size = 0;
        objects = new T[10];
    }

    public int Count { get { return size; } }

    public void Add(T pers)
    {
        objects[size] = pers;
        size++;
    }

    public T Get(int index) { return objects[index]; }
}

public class Politics
{
    public Associate<IGovernment> Create()
    {
        IGovernment gov = new Country();
        gov.Continent = "Africa";
        gov.Name = "Sénégal";

        Associate<IGovernment> country = new Associate<IGovernment>();
        country.Add(gov);

        return country;
    }
}

Introduction to Built-In Generic Interfaces

To assist you in creating generic classes, the .NET Framework provides a large library of generic classes and interfaces.

Constraints in a Generic Class

Constraining a Parameter to a Structure

When creating a generic class, we saw that you can indicate that it would use a parameter type but you are not specifying the type of that parameter. You can put a restriction to indicate how the compiler should deal with the parameter. You have many options.

To create a constraint on a generic class, after the <type-name> operator, type where TypeName : followed by the rule that the class must follow. The basic formula to create a generic restriction is:

class class-name<parameter-type>
where T : constraint rule(s)
{

}

As we will see, there are various types of constraints you can apply to generic classes.

Constraining a Parameter to a Structure Type

When creating a generic class, you can indicate that you want the parameter type to be a structure. To do this, set the constraint rule to struct. Here is an example:

class Rectangle<T>
    where T : struct
{
}

In this example, the where restriction indicates that a vertex must be a data type that is a structure. Here is an example:

public struct NaturalPoint
{
    public int X;
    public int Y;

    public NaturalPoint(int x = 0, int y = 0)
    {
        X = x;
        Y = y;
    }
}

public struct FloatingPoint
{
    public double X;
    public double Y;

    public FloatingPoint(double x = 0d, double y = 0d)
    {
        X = x;
        Y = y;
    }
}

public class Rectangle<T>
    where T : struct
{
    public T vertex1;
    public T vertex2;
    public T vertex3;
    public T vertex4;

    public Rectangle(T one, T two, T three, T four)
    {
        vertex1 = one;
        vertex2 = two;
        vertex3 = three;
        vertex4 = four;
    }
}

public class Exercise
{
    private void Create()
    {
        NaturalPoint npt1 = new NaturalPoint(0, 2);
        NaturalPoint npt2 = new NaturalPoint(2, 0);
        NaturalPoint npt3 = new NaturalPoint(0, -2);
        NaturalPoint npt4 = new NaturalPoint(-2, 0);

        Rectangle<NaturalPoint> rect1 = 
        		new Rectangle<NaturalPoint>(npt1, npt2, npt3, npt4);

        FloatingPoint fpt1 = new FloatingPoint( 3,  5);
        FloatingPoint fpt2 = new FloatingPoint( 3, -5);
        FloatingPoint fpt3 = new FloatingPoint(-5, -5);
        FloatingPoint fpt4 = new FloatingPoint(-5,  3);

        Rectangle<FloatingPoint> rect2 = 
        		new Rectangle<FloatingPoint>(fpt1, fpt2, fpt3, fpt4);
    }
}

Constraining a Parameter to a Class Type

To indicate that you want the parameter type of a generic class to be a class type, set the constraint rule to class. Here is an example:

class Rectangle<T>
    where T : class
{
}

The where restriction in this case indicates that the T parameter must have been created from a class.

Constraining a Parameter to a Specific Class

Imagine you create a regular interface such as the following:

public interface IPerson
{
    string FullName { get; set; }
    DateTime DateofBirth { get; set; }
}

Then imagine you implement it in class. Here is an example:

public interface IPerson
{
    string? FullName { get; set; }
    string? DateofBirth { get; set; }
}
public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName    = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

When creating a generic class, you can make it implement the functionality of a certain interface or you can make sure that the class is derived from a specific base class. This would make sure that the generic class contains some useful functionality.

To create a constraint on a generic class, after the <type-name> operator, type where type-name : followed by the rule that the class must follow. For example, you may want the generic class to implement the functionality of a pre-defined class. You can create the generic class as follows:

public interface IPerson
{
    string? FullName { get; set; }
    string? DateofBirth { get; set; }
    void Display();
}

public class Employee<T>
	where T : PersonalIdentification
{

}

After creating the class, you must implement the virtual members of the where class/interface, using the rules of generic classes, the way we have done it so far.

public interface IPerson
{
    string? FullName    { get; set; }
    string? DateofBirth { get; set; }
}
public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName    = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : PersonalIdentification
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

When declaring a variable for the generic class, in its <> operator, you must enter an object of the base class. Here is an example:

using static System.Console;

Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
            
var std = new PersonalIdentification("James Sandt", "12/08/2002");

Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
empl.Identification = std;

WriteLine("Full Name: {0}", empl.Identification.FullName);
WriteLine($"Date Of birth: {empl.Identification.DateofBirth}");

WriteLine("=================================");

public interface IPerson
{
    string FullName { get; set; }
    string DateofBirth { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName = name;
    }

    public virtual string? FullName { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : PersonalIdentification
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

This would produce:

Personal Identification
---------------------------------
Full Name: James Sandt
Date Of birth: 12/08/2002
=================================
Press any key to continue . . .

Consider the following Physician class:

public interface IPerson
{
    string FullName { get; set; }
    string DateofBirth { get; set; }
}

public interface IProfession
{
    string Category { get; set; }
}

public class Physician
{
    public string Category { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName    = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : PersonalIdentification
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

Based on the restrictions, you cannot use just any class as the parameter of the generic. For example, the following would produce an error:

using static System.Console;

Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
            
var std = new PersonalIdentification("James Sandt", "12/08/2002");

Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
empl.Identification = std;

WriteLine(string.Format("Full Name:     {0}", empl.Identification.FullName));
WriteLine(string.Format("Date Of birth: {0}", empl.Identification.DateofBirth));
            
var doctor = new Physician();
doctor.Category = "Health Care";
Employee<Physician> rn = new Employee<Physician>();

WriteLine("=================================");

public interface IPerson
{
    string FullName { get; set; }
    string DateofBirth { get; set; }
}

public interface IProfession
{
    string Category { get; set; }
}

public class Physician
{
    public string Category { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName = name;
    }

    public virtual string? FullName { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : PersonalIdentification
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

This would produce:

Error	1	The type 'Physician' cannot be used as type 
parameter 'T' in the generic type or method 'Employee<T>'.
There is no implicit reference conversion from 'Physician' 
to 'PersonalIdentification'.	
C:\Temporary Projects\WhereGeneric\Exercise.cs	115	18	WhereGeneric

You can also create a constraint so that a generic class implements an interface.

A new Default Restriction

Depending on the behavior you want a class to have, you may want to require that a generic class that uses a parameter must also have a default constructor. To put this restriction, you use the new keyword as a constraint. The primary formula to follow is:

class ClassName<T> where T : new()

The new factor in this formula is the new keyword. Here is an example of using it:

public interface IPerson
{
    string FullName    { get; set; }
    string DateofBirth { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName    = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : new()
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

This new operator indicates that the class (or structure) that the T parameter represents must have a default constructor. Remember that when this code executes, the Employee<> class doesn't know what T means or represents. This means that the above code will compile just fine. It is when you declare a variable of the Employee<> type that the compiler is informed about the T parameter. That's when it checks the class that T represents. If that class doesn't have a default constructor, you would receive an error. Here is an example:

using static System.Console;

Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");
            
var std = new PersonalIdentification("James Sandt", "12/08,2002");

Employee<PersonalIdentification> empl =
                new Employee<PersonalIdentification>();

empl.Identification = std;

WriteLine("=================================");

public interface IPerson
{
    string FullName { get; set; }
    string DateofBirth { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : new()
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

This would produce:

Error	1	'PersonalIdentification' must be a non-abstract type with a
  public parameterless constructor in order to use it as parameter 'T'
  in the generic type or method 'Employee<T>'
  C:\. . .\Temporary Projects\WhereGeneric\Exercise.cs
  	71	18	WhereGeneric

The correction is to make sure that the class that T represents has a default constructor. Here is an example:

public interface IPerson
{
    string FullName    { get; set; }
    string DateofBirth { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification()
    {
    }

    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName    = name;
    }

    public virtual string? FullName    { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T>
    where T : new()
{
    public Employee()
    {
    }

    public Employee(T record)
    {
        Identification = record;
    }

    public T Identification { get; set; }
}

Constraining Various Parameters

Remember that a generic class can use more than one parameter. Here is an example:

public class Employee<T, P>
{
}

If you want to set a restriction of the parameters, use a where operator for each. Here is an example:

public interface IPerson
{
    string FullName    { get; set; }
    string DateofBirth { get; set; }
}

public interface IProfession
{
    string Category { get; set; }
}

public class Profession
{
    public string Category { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName = name;
    }

    public virtual string? FullName { get; set; }
    public virtual string? DateofBirth { get; set; }
}

public class Employee<T, P>
    where T : PersonalIdentification
    where P : Profession
{
    public Employee()
    {
    }

    public Employee(T record, P earn)
    {
        Occupation     = earn;
        Identification = record;
    }

    public T Identification { get; set; }
    public P Occupation     { get; set; }
}

When declaring a variable for the class, provide the appropriate type. Here is an example:

using static System.Console;

Title = "Personal Identification";
WriteLine("Personal Identification");
WriteLine("---------------------------------");

var persID = new PersonalIdentification("Jeannot Schwartz", "12/08,2002");

var pro = new Profession();
pro.Category = "Health Care";

Employee<PersonalIdentification, Profession> surgeon =
        new Employee<PersonalIdentification, Profession>();

surgeon.Identification = persID;
surgeon.Occupation = pro;

WriteLine("Category: {0}", surgeon.Occupation.Category);
WriteLine("=================================");

public interface IPerson
{
    string FullName    { get; set; }
    string DateofBirth { get; set; }
}

public interface IProfession
{
    string Category { get; set; }
}

public class Profession
{
    public string Category { get; set; }
}

public class PersonalIdentification : IPerson
{
    public PersonalIdentification(string name, string dob)
    {
        DateofBirth = dob;
        FullName = name;
    }

    public virtual string?? FullName    { get; set; }
    public virtual string?? DateofBirth { get; set; }
}

public class Employee<T, P>
        where T : PersonalIdentification
        where P : Profession
{
    public Employee() { }

    public Employee(T record, P earn)
    {
        Occupation = earn;
        Identification = record;
    }

    public T Identification { get; set; }
    public P Occupation     { get; set; }
}

This would produce:

Personal Identification
---------------------------------
Category: Health Care
=================================
Press any key to continue . . .

Remember that any regular class can implement an interface. In the same way, a normal generic class can implement any interface of your choice. Here is an example:

public class Employee<T> 
where T : IPerson
{
}

On such a class, you can also put a restriction that the class that T represents must have a default constructor. In this case, the new operator must be set as the last. Here is an example:

public class Employee<T> 
where T : IPerson, new()

Previous Copyright © 2008-2022, FunctionX Tuesday 19 October 2021 Next