Why and when using where keyword within a generic class context


Introduction

In this article, I will try to make understand the importance of the where keyword used as a part of a generic class context through answering two questions why? And When or in witch case the where keyword is used.

Why?

"Where" keyword is used to specify certain constraints according to types those used as generics, I mean "where" is used to attach the T undefined generic type to a special and already defined type.

When?

As an answer to this question, I give this example. Suppose that you have an abstract class named Point and its implementation is as under:

public abstract class Point

{

    public Point() { }

    public abstract string Name

    {

        get;

        set;

    }

public abstract void Reset();

public abstract int Dimension();
}

This above class is extended by 2 news derived classes whose are _2DPoint<U> and_3DPoint<U> , then this is an implementation of both classes, bellow:

_2DPoint:

public class _2DPoint<U>:Point

{

    private U _x;

    private U _y;

    private string _Name;

    public _2DPoint():base() {}

    public _2DPoint(U x, U y,string Name)

    {

        this.x = x;

        this.y = y;

        this.Name = Name;

    }

    
   
public U x

    {

        get { return _x; }

        set { _x = value; }

    }

    public U y

    {

        get { return _y; }

        set { _y = value; }

    }

    public override string  Name

    {

        get

        {
            return _Name;
        }

        set

        {
            _Name = value;
        }
    }

    public override int Dimension()

    {
        return 2; 
    }

    public override void Reset()

    {

        x = default(U);

        y = default(U);

    }

}

_3DPoint:


class
_3DPoint<U>:Point

{

    private U _x;

    private U _y;

    private U _z;

    private string _Name;

    public _3DPoint():base() { }

    public _3DPoint(U x, U y,U z, string Name)

    {

        this.x = x;

        this.y = y;

        this.z = z;

        this.Name = Name;

    }

 

    public U x

    {

        get { return _x; }

        set { _x = value; }

    }

    public U y

    {

        get { return  _y; }

        set { _y = value; }

    }

    public U z

    {

        get { return _z; }

        set { _z = value; }

    }

    public override string Name

    {

        get

        { return _Name; }

        set

        { _Name = value; }

    }

    public override int Dimension()

    { return 3; }

    public override void Reset()

    {

        x = default(U);

        y = default(U);

        z = default(U);

    }

}

Assume now that we want to build a custom container to stock all point objects, giving that this custom container will be defined as generic like bellow:

public class PointCollection<T>

{

    List<T> oList;

    public PointCollection()

    {

        oList = new List<T>();

    }

    public T this[int index]

    {

        get { return oList[index]; }

        set { oList[index] = value; }

    }

    public void AddItem(T element)

    {

        oList.Add(element);

    }

}

This object container is so flexible that it can contain different versions and kind of objects. Now, what if you try to add a method that gives us the dimension of a point given that they are two kinds of derived points the 2 dimensions one and the 3 dimensions one. To do this, I try to get the dimension off the oList element as so:



As you remark, when I try to get the dimension through elements within the oList, I remark that they behave like an object type, take a glance at the intellisense just above.

Now, if you just change the container class declaration instead of the first one

public class PointCollection<T> where T : Point

{

    List<T> oList;

    public PointCollection()

    {

        oList = new List<T>();

    }
}
A
s you remark, I just add where T: Point at the top, you can observe the difference at the intellisense level. Elements within oList behave as a point instead of object now:



Also the entire code according to the class container will be as follow:

public class PointCollection<T> where T : Point

{

    List<T> oList;

    public PointCollection()

    {

        oList = new List<T>();

    }

    public T this[int index]

    {

        get { return oList[index]; }

        set { oList[index] = value; }

    }

    public void AddItem(T element)

    {

        oList.Add(element);

    }

    public void GetDimension(int index)

    {

        MessageBox.Show(oList[index].Dimension().ToString());

    }

}

If you want to try the code, you can add some derived points elements within the scope of your main bloc of code as follow:

void
Main(string[]args)

{                                                                                                                                                      

    PointCollection<Point> x = new PointCollection<Point>();

    x.AddItem(new _2DPoint<int>(1,1,"p1"));

    x.AddItem(new _3DPoint<double>(1,2,1,"p2"));

    x.AddItem(new _3DPoint<int>(1, 2, 3, "p3"));

    x.GetDimension(0);

}

"Where", keyword can also be used to attach constraint to parameter type of a given generic methods or delegates.

Next Recommended Readings