Home

Creating and Using Generic Classes

   

Constraining 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.

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

class ClassName<ParameterType>
where T : Constraint Rule
{

}

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:

using System;

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
{
    static int Main()
    {
        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);

        return 0;
    }
}

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 class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }
}

You may be tempted to derive just any type of class from it. One of the features of generics is that you can create a class that must implement the functionality of a certain class of your choice. For example, when creating a generic class, you can oblige it to 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 <TypeName> operator, type where TypeName : 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; }
    DateTime 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. 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 System;

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

public class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }
}

public class Employee<T>
where T : PersonalIdentification
{
    private T info;

    public Employee()
    {
    }

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

    public T Identification
    {
        get
        {
            return info;
        }

        set
        {
            info = value;
        }
    }
}

public class Exercise
{
    static int Main()
    {
        var std = new PersonalIdentification("James Sandt");
        std.DateofBirth = new DateTime(2002, 12, 8);

        Employee<PersonalIdentification> empl = 
        		new Employee<PersonalIdentification>();
        empl.Identification = std;
        
        Console.WriteLine("Full Name:     {0}", 
        		empl.Identification.FullName);
        Console.WriteLine("Date Of birth: {0}", 
        		empl.Identification.DateofBirth.ToShortDateString());

        Console.WriteLine();
        return 0;
    }
}

This would produce:

Full Name:     James Sandt
Date Of birth: 12/8/2002

Press any key to continue . . .

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

using System;

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

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

public class Physician
{
    private string type;

    public string Category
    {
        get
        {
            return type;
        }

        set
        {
            type = value;
        }
    }
}

public class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification()
    {
        _name = "Unknown";
    }
    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }
}

public class Employee<T>
where T : PersonalIdentification
{
    private T info;

    public Employee()
    {
        
    }

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

    public T Identification
    {
        get
        {
            return info;
        }

        set
        {
            info = value;
        }
    }
}

public class Exercise
{
    static int Main()
    {
        var std = new PersonalIdentification("James Sandt");
        std.DateofBirth = new DateTime(2002, 12, 8);

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

        Console.WriteLine();
        return 0;
    }
}

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 new. Here is an example of using it:

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

public class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }
}

public class Employee<T>
where T : new()
{
    private T info;

    public Employee()
    {
    }

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

    public T Identification
    {
        get
        {
            return info;
        }

        set
        {
            info = value;
        }
    }
}

public class Exercise
{
    static int Main()
    {
        return 0;
    }
}

This 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:

public class Exercise
{
    static int Main()
    {
        var std = new PersonalIdentification("James Sandt");
        std.DateofBirth = new DateTime(2002, 12, 8);

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

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:

using System;

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

public class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification()
    {
    }

    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }
}

public class Employee<T>
where T : new()
{
    private T info;

    public Employee()
    {
    }

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

    public T Identification
    {
        get
        {
            return info;
        }

        set
        {
            info = value;
        }
    }
}

public class Exercise
{
    static int Main()
    {
        var std = new PersonalIdentification("James Sandt");
        std.DateofBirth = new DateTime(2002, 12, 8);

        Employee<PersonalIdentification> empl = 
        		new Employee<PersonalIdentification>();
        empl.Identification = std;
        
        Console.WriteLine("Full Name:     {0}",
        		empl.Identification.FullName);
        Console.WriteLine("Date Of birth: {0}", 
        		empl.Identification.DateofBirth.ToShortDateString());  

        Console.WriteLine();
        return 0;
    }
}

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:

using System;

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

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

public class Profession
{
    private string type;

    public string Category
    {
        get
        {
            return type;
        }

        set
        {
            type = value;
        }
    }
}

public class PersonalIdentification : IPerson
{
    private string _name;
    private DateTime _dob;

    public PersonalIdentification()
    {
        _name = "Unknown";
    }
    public PersonalIdentification(string name)
    {
        _name = name;
        _dob = new DateTime(0);
    }

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

    public DateTime DateofBirth
    {
        get { return _dob; }
        set
        {
            _dob = value;
        }
    }

    public virtual void Display()
    {
        Console.WriteLine("Full Name:     {0}", _name);
        Console.WriteLine("Date Of birth: {0}", _dob.ToShortDateString());
    }
}

public class Employee<T, P>
where T : PersonalIdentification
where P : Profession
{
    private T info;
    private P earner;

    public Employee()
    {
        
    }

    public Employee(T record, P earn)
    {
        info = record;
        earner = earn;
    }

    public T Identification
    {
        get
        {
            return info;
        }

        set
        {
            info = value;
        }
    }

    public P Occupation
    {
        get
        {
            return earner;
        }

        set
        {
            earner = value;
        }
    }
}

public class Exercise
{
    static int Main()
    {
        var persID = new PersonalIdentification();
        persID.FullName = "Jeannot Schwartz";
        persID.DateofBirth = new DateTime(2002, 12, 8);

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

        Employee<PersonalIdentification, Profession> surgeon =
		new Employee<PersonalIdentification, Profession>();
        surgeon.Identification = persID;
        surgeon.Occupation = pro;
        surgeon.Identification.Display();
        Console.WriteLine("Category: {0}", surgeon.Occupation.Category); 

        Console.WriteLine();
        return 0;
    }
}

This would produce:

Full Name:     Jeannot Schwartz
Date Of birth: 12/8/2002
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 an 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()
{
}

Introduction to Generic Classes and Inheritance

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:

using System;

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 void ShowCharacteristics()
        {
            Console.WriteLine("Geometric Figure: {0}", Name);
            Console.WriteLine("Description:      {0}", Describe());
            Console.WriteLine("Base:             {0}", Base);
            Console.WriteLine("Height:           {0}", Height);
        }
}

public class Exercise
{
    static int Main()
    {
        // Trapezoid with equal sides
        var Kite = new Quadrilateral<double>("Beach Kite", 18.64, 18.64);
        Kite.ShowCharacteristics();
        Console.WriteLine();

        // Rectangle, in meters
        var BasketballStadium = new Quadrilateral<Byte>();
        BasketballStadium.Name = "Basketball Stadium";
        BasketballStadium.Base = 15;
        BasketballStadium.Height = 28;
        BasketballStadium.ShowCharacteristics();
        Console.WriteLine();

        return 0;
    }
}

This would produce:

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 geometric figure with four sides
Base:             15
Height:           28

Press any key to continue . . .

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 of 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 as you see fit. 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 as you see fit. You can then declare a variable of the class and use it as we done so far for other generic classes. Here is an example:

using System;

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 void ShowCharacteristics()
        {
            Console.WriteLine("Geometric Figure: {0}", Name);
            Console.WriteLine("Description:      {0}", Describe());
            Console.WriteLine("Base:             {0}", Base);
            Console.WriteLine("Height:           {0}", 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 void ShowCharacteristics()
        {
            Console.WriteLine("Geometric Figure: {0}", Name);
            Console.WriteLine("Description:      {0}", Describe());
            Console.WriteLine("                  {0}", Describe());
            Console.WriteLine("Side:             {0}", Base);
        }
}

public class Exercise
{
    static int Main()
    {
        // Rectangle, in meters
        var plate = new Square<Byte>();

        plate.Name   = "Plate";
	plate.Base   = 15;
        plate.Height = 28;
	plate.ShowCharacteristics();

        Console.WriteLine();
        return 0;
    }
}

This would produce:

Geometric Figure: Plate
Description:      A quadrilateral is a geometric figure with four sides
                  A square is a quadrilateral with four equal sides
Side:             15

Press any key to continue . . .
 

Previous Copyright © 2010-2011 FunctionX Home