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:

namespace Exercises.Models
{
    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:

@page
@model Exercises.Pages.ExerciseModel
@{
    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);
}

@functions{
    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;
        }
    }
}

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:

namespace Exercises.Models
{
    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:

namespace Exercises.Models
{
    public interface IPerson
    {
        string FullName { get; set; }
        DateTime DateofBirth { get; set; }
    }
}

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

namespace Exercises.Models
{
    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:

namespace Exercises.Models
{
    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.

namespace Exercises.Models
{
    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:

@page
@model Exercises.Pages.ExerciseModel
@{
    var std = new PersonalIdentification("James Sandt", "12/08/2024");
    
    Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
    
    empl.Identification = std;
}

@functions{
    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; }
    }
}

<pre>Personal Identification
---------------------------------
Full Name:     @empl.Identification.FullName
Date Of birth: @empl.Identification.DateofBirth
=================================</pre>

This would produce:

Personal Identification
---------------------------------
Full Name: James Sandt
Date Of birth: 12/08/2024
=================================

Consider the following Physician class:

namespace Exercises.Models
{
    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:

@page
@model Exercises.Pages.ExerciseModel
@{
    var std = new PersonalIdentification("James Sandt", "12/08/2024");
    
    Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
    
    empl.Identification = std;
}

@functions{
    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; }
    }
}

<pre>Personal Identification
---------------------------------
Full Name:     @empl.Identification.FullName
Date Of birth: @empl.Identification.DateofBirth
=================================</pre>

@{
    var doctor = new Physician();
    
    doctor.Category = "Health Care";
    Employee<Physician> rn = new Employee<Physician>();
}

This would produce:

Severity	Code	Description	Project	File	Line	Suppression State
Error (active)	CS0311	The type 'Exercises.Pages.Pages_Exercise.Physician' cannot be used as type parameter 'T' in the generic type or method 'Pages_Exercise.Employee<T>'. There is no implicit reference conversion from 'Exercises.Pages.Pages_Exercise.Physician' to 'Exercises.Pages.Pages_Exercise.PersonalIdentification'.	Exercises	F:\Users\mything\source\repos\RazorPages\Exercises\Exercises\Pages\Exercise.cshtml	66

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:

namespace Exercises.Models
{
    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:

@page
@model Exercises.Pages.ExerciseModel
@{
    var std = new PersonalIdentification("James Sandt", "12/08,2025");
    
    Employee<PersonalIdentification> empl = new Employee<PersonalIdentification>();
    
    empl.Identification = std;
}

@functions{
    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:

Severity	Code	Description	Project	File	Line	Suppression State
Error (active)	CS0310	'Pages_Exercise.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 'Pages_Exercise.Employee<T>'	Exercises	F:\Users\mything\source\repos\RazorPages\Exercises\Exercises\Pages\Exercise.cshtml	6

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

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Exercises.Pages
{
    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:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Exercises.Pages
{
    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:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Exercises.Pages
{
    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:

@page
@model Exercises.Pages.ExerciseModel
@{
    var persID = new PersonalIdentification("Jeannot Schwartz", "12/08,2025");

    var pro = new Profession();

    pro.Category = "Health Care";
    
    Employee<PersonalIdentification, Profession> surgeon = new Employee<PersonalIdentification, Profession>();
    
    surgeon.Identification = persID;
    surgeon.Occupation = pro;
}

@functions{
    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; }
    }
}

<pre>Personal Identification
---------------------------------
Category: @surgeon.Occupation.Category
=================================</pre>

This would produce:

Personal Identification
---------------------------------
Category: Health Care
=================================

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:

namespace Exercises.Models
{
    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:

@functions{
    public class Employee<T> 
	where T : IPerson, new()
}

Previous Copyright © 2008-2022, FunctionX Saturday 05 March 2022 Home