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 close this window . . .

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 close this window . . .

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 © 2012-2026, FunctionX Tuesday 19 October 2021 Next