Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C#

C# Generics for beginners, Part 2

Rate me:
Please Sign up or sign in to vote.
4.95/5 (20 votes)
12 Jun 2017CPOL3 min read 22K   36   4
We continue exploring the basics of generics.

Article Index

C# Generics for Beginners - Part 1

C# Generics for Beginners - Part 2

Introduction

In part 1, we covered type parameters. The example generic class we built did not do a lot. Basically all it did was store values. Now lets see how to build some logic into a generic class.

What the compiler knows.

To understand generics, you need to keep in mind what the compiler knows, and what it cannot possibly know. 

Consider the following generic class:

C#
public class GenericDemo<T>
{
    public void Foo(T Value)
    {
        //what does the compiler know about Value?
    }
}

There is only one thing the compiler knows for certain about the Value parameter: it is of type Object. Whoever creates an instance of this generic class can pass through ANY type as the type parameter. The only common thing between all the possibilities is that they must be of type Object. 

The implication of this is that inside the Foo method, we can only do things with the Value parameter we can do with an object.

Check out the documentation for object here:

https://msdn.microsoft.com/en-us/library/system.object(v=vs.110).aspx

As you can see what you can do with Value is limited.

So how can we provide the compiler with more information about the T type parameter in this example? We can tell the compiler more about the type parameter by adding constraints.

For reference. here is the .Net documentation for type parameter constraints: https://msdn.microsoft.com/en-us/library/d5x73970.aspx

So lets add a constraint.

C#
public class GenericDemo<T> where T : IComparable<T>
{
    public T GetBiggerValue(T value1, T value2)
    {
        if (value1.CompareTo(value2) >= 0) return value1;
        return value2;
    }
}

The "where T : iComparable<T>" bit on the first line, is telling the compiler that the type parameter T must implement the IComparable<T> interface. And since value1, and value2 are both of type T, the CompareTo method is now available to be called, where previously it was not.

We can call the GetBiggerValue method like so:

C#
var biggestString = GenericDemo<string>.GetBiggerValue("b", "a"); //"b":
var biggestInt = GenericDemo<int>.GetBiggerValue(123, 456); //456

We are allowed to pass int, and string as type parameters, because they both implement IComparable.

This does NOT compile:

C#
var doesNotWork = GenericDemo<char[]>.GetBiggerValue("a".ToCharArray(), "b".ToCharArray()); //DOES NOT COMPILE!

The reason it does not compile, is because the type char[] does NOT implement IComparable, and the constraint we have added requires all type parameters passed through to implement IComparable.

Other Constraint Types

Lets take a quick look at the other constraint types.

Parameterless Constructor Constraint

C#
public class HasParameterlessConstructor
{
    public HasParameterlessConstructor() { }
}
public class DoesNotHaveParameterlessConstructor
{
    public DoesNotHaveParameterlessConstructor(int parameter1, string paramter2) { }
}
public class GenericDemo<T> where T : new()
{
    public static T CreateNewInstance()
    {
        var newInstance = new T();
        return newInstance;
    }
}

The constraint in the example above is "where T : new()". This tells the compiler that the type T, must have a parameterless constructor. In the example you will see two classes as well called HasParameterlessConstructor and DoesNotHaveParameterlessConstructor.

Here is a demonstration of this constraint in action:

C#
var compiles = GenericDemo<HasParameterlessConstructor>.CreateNewInstance();
var alsoCompiles = GenericDemo<List<int>>.CreateNewInstance();
var doesNotCompile = GenericDemo<DoesNotHaveParameterlessConstructor>.CreateNewInstance();

The first two lines both compile since HasParameterlessConstructor and List<int> both have a parameterless constructor. This means that we can create a new instance of T by calling "new T()", like we do inside the CreateNewInstance method.

The third line, does NOT compile, because DoesNotHaveParameterlessConstructor has no constructors that take no parameters. "new T()" is impossible for DoesNotHaveParameterlessConstructor. You can only create an instance of DoesNotHaveParameterlessConstructor by calling new T(1,"a"), since its constructor takes an int, and a string, and it has no other constructors.

Reference and Value type Constraints

See this article for more information on reference and value types.

https://msdn.microsoft.com/en-us/library/t63sy5hs.aspx?f=255&MSPPError=-2147217396

The 'class' constraint in the following example tells the compiler that T must be a reference type. 

C#
public class GenericDemo<T> where T : class, IComparable<T> //T must be a reference type
{
    public static T GetBiggerValue(T value1, T value2)
    {
        if (value1.CompareTo(value2) >= 0) return value1;
        return value2;
    }
}

The 'struct' constraint in the following example tells the compiler that T must be a value type. 

C#
public class GenericDemo<T> where T : struct, IComparable<T> //T must be a value type
{
    public static T GetBiggerValue(T value1, T value2)
    {
        if (value1.CompareTo(value2) >= 0) return value1;
        return value2;
    }
}

Basic Example

Lets build a rudimentary example of a generic class that uses constraints, that has some practical use. 

What this example does is allows you to add jobs to a collection, and they will then be executed in order of priority.

C#
public interface IJob
{
    int GetPriority();
    void Execute();
}

public class JobRunner<T> where T : IJob, IComparable<T>
{
    private List<T> _jobList = new List<T>();

    private class JobComparer : IComparer<T>
    {
        public int Compare(T x, T y)
        {
            return x.CompareTo(y);
        }
    }

    public void AddJob(T job)
    {
        _jobList.Add(job);
    }

    public void ExecuteJobs()
    {
        //order jobs according to priority. Note that the JobComparer is not neccesary,
        //this is only to demonstrate how the IComparer constraint adds information the
        //compiler can use.
        var sortedByPriority = _jobList.OrderByDescending(x => x, new JobComparer());
        foreach (var job in sortedByPriority)
        {
            job.Execute();
        }
    }
}

public class WriteToConsoleJob : IJob, IComparable<IJob>
{
    private int _priority;
    private string _toWrite;

    public WriteToConsoleJob(int priority, string toWrite)
    {
        _toWrite = toWrite;
        _priority = priority;
    }

    public int GetPriority()
    {
        return _priority;
    }

    public void Execute()
    {
        Console.WriteLine(_toWrite);
    }

    public int CompareTo(IJob other)
    {
        return this.GetPriority().CompareTo(other.GetPriority());
    }
}

This generic class can be used like so:

C#
var jobRunner = new JobRunner<WriteToConsoleJob>();

var highPriorityJob = new WriteToConsoleJob(1000, "high priority job");
var lowPriorityJob = new WriteToConsoleJob(50, "low priority job");
var mediumPriorityJob = new WriteToConsoleJob(500, "medium priority job");

jobRunner.AddJob(lowPriorityJob);
jobRunner.AddJob(highPriorityJob);
jobRunner.AddJob(mediumPriorityJob);

jobRunner.ExecuteJobs();

The output on the console will be:

C#
high priority job
medium priority job
low priority job

This line:

C#
public class JobRunner<T> where T : IJob, IComparable<T>

Tells the compiler that T must implement both IJob, as well as IComparable. IJob allows GetPriority() and Execute() to be called on instances of type T. IComparable allows CompareTo to be called. So you can create your own jobs that do anything you want, as long as it implements both IJob, and IComparable.

Conclusion

We now understand what type parameter constraints do. The knowledge you have now should allow you to build your own generic classes. It should also allow you to understand most C# that use generics you come across.

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionClear and precise Pin
Hailu Worku Obsse14-Jun-17 21:05
professionalHailu Worku Obsse14-Jun-17 21:05 
QuestionNice Explanation Pin
Erick Moisés14-Jun-17 7:00
Erick Moisés14-Jun-17 7:00 
GeneralMy vote of 5 Pin
PVX00727-Nov-15 11:57
PVX00727-Nov-15 11:57 
GeneralRe: My vote of 5 Pin
Bertus van Zyl29-Nov-15 18:05
Bertus van Zyl29-Nov-15 18:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.