Contents
  1. 1. Why do we need it?
  2. 2. How does it work?
  3. 3. Constraints

Why do we need it?

Generic allows developers create classes or methods work with any data type.

Let’s take a look at the following example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//List.cs
using System;

namespace Generic
{
public class List
{
public void Add(int value)
{
throw new NotImplementedException();
}

public int this[int index]
{
get
{
throw new NotImplementedException();
}
}
}
}

It’s a normal List class provides integer input and retrieval. How about we want the List class to provide input and retrieval with another data type? We need to create another class with another data type handle in traditional way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//StringList.cs
using System;

namespace Generic
{
public class StringList
{
public void Add(string value)
{
throw new NotImplementedException();
}

public string this[int index]
{
get
{
throw new NotImplementedException();
}
}
}
}

Yes it works. But it always requires us to create new classes to process different data types, which will create a lot of code duplications. If there are any logic changes, we need to make code changes to all the places and it’s really unproductive. So the solution would be using a list of object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ObjectList.cs
using System;

namespace Generic
{
public class ObjectList
{
public void Add(object value)
{

}

public object this[int index]
{
get
{
throw new NotImplementedException();
}
}
}
}

But there’s another problem here is performance. If we use this class to store the primitive value types like integer, every time we insert a primitive value type into the list and it has to be boxed to be stored as an object. When we access the value, it has to be un-boxed and cause performance penalty. Or even we use reference types we want to cast an object like a book to an object or vice versa, it is casting again has performance penalty. So generics came as solution to resolve this problem. With generics we create a class once and reuse it multiple times without performance penalty.

How does it work?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//GenericList.cs
using System;

namespace Generic
{
//T is usually short for Template or Type
public class GenericList<T>
{
// We don't know what is T. The consumer of this class will specify that
public void Add(T value)
{

}

public T this[int index]
{
get { throw new NotImplementedException();}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Program.cs        --This is how we use generic type
namespace Generic
{
class Program
{
static void Main(string[] args)
{
// var stringList = new StringList();
// stringList.Add("ABC");
//
// var numbers = new List();
// numbers.Add(1);

var numbers = new GenericList<int>();
numbers.Add(1);

var stringList = new GenericList<string>();
stringList.Add("ABC");
}
}
}

Now we don’t have performance penalty because our generic list is actually a list of integers/strings at runtime. It’s not a list of objects. No casting or boxing.

For most of the time we don’t need to create the generic list by ourselves. We will use the generic lists that are actually part of .net.

Constraints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

namespace Generic
{
public class Utilities
{
public int Max(int a, int b)
{
return a > b ? a : b;
}

public T Max<T>(T a, T b) where T : IComparable
{
/*
* The compiler doesn't know the type of T, so it can't apply comparison between a and b(like the above method).
* At this point, it thinks A and B are both objects.
* We want to assume both A and B implement the IComparable interface which provides a method called CompareTo() to compare two objects.
* This is a use case to apply a constraint at the end of the method.
*/
return a.CompareTo(b) > 0 ? a : b;
}
}
}

The above example shows how to create a generic method inside a non generic class because we don’t always have to start with the generic class. We can also move the generic constraint to the class level as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

namespace Generic
{
//There are 5 types of constraints we can apply
//1. where T : IComparable (Constraint to an interface)
//2. where T : Product (Constraint to a class, e.g T is a product or any of its children/subclasses)
//3. where T : struct (Constraint to a value type)
//4. where T : class (Constraint to be a reference type)
//5. where T : new() (Constraint to an object that has a default constructor)
public class Utilities<T> where T : IComparable
{
public int Max(int a, int b)
{
return a > b ? a : b;
}

public T Max(T a, T b)
{
return a.CompareTo(b) > 0 ? a : b;
}
}
}
Contents
  1. 1. Why do we need it?
  2. 2. How does it work?
  3. 3. Constraints