Click here to Skip to main content
15,892,809 members
Articles / Programming Languages / C#

C# Generics - A few key points

Rate me:
Please Sign up or sign in to vote.
4.00/5 (3 votes)
19 Feb 2013CPOL6 min read 9.6K   8   3
Discussing few points about generics in .net and what it offers.

Introduction

We (should) always strive for perfection. The very reason for advent of computers is to reduce humane efforts. Although, it has not gone that way but for sure we are capable to handle much more today and a lot more complex things.

Generics in C# isn’t an invention in itself. Something of this sort was expected as template was available there in its predecessor C++.

For some reason I could never understand the real usage of template in C++. It was definitely not as broad as we see today with C#. From 2.0 to 3.5 to 4.0 it has matured a lot – Let’s see what it offers:

There are certain things that will never change.

Like you have n number of objects and you want to place them in an order. In computational term you would like to sort them ascending or descending. If those objects are comparable then with a logic like (bubble, binary..etc.) it can be sorted. So each class doesn’t need to define the sort method. What they need is to implement IComparable interface.

See the code here:

C#
public class Person : IComparable
{
  public Double NetWorth = 10000;
  public String Name = "xyz";
  public int CompareTo(object obj)
  {
      return NetWorth.CompareTo((obj as Person).NetWorth);
  }
}

public class Individual
{
     public Double NetWorth = 10000;
     public String Name = "abc";
}

These two classes are similar – apart from the IComparable implementation. Now I write a test like following:

C#
List<Person> persons = new List<Person>();
persons.Add(new Person() { Name = "One", NetWorth = 10000 });
persons.Add(new Person() { Name = "Two", NetWorth = 1000 });            

persons.Sort();

List<Individual> individuals = new List<Individual>();
             
individuals.Add(new Individual() { Name = "One", NetWorth = 10000 });
individuals.Add(new Individual() { Name = "Two", NetWorth = 1000 });
individuals.Sort(); // Invalid operation exception will be thrown here at runtime

The framework developer has written sort functionality in List<T> based on IComparable interface. And we can leverage that by implementing fairly simple IComparable interface in our classes.

Things get easy this way. First we create interfaces based on our understanding/system-requirements and then we build a framework on top of that – which handles all/most of the complexity. And then leveraging the framework we develop our projects – that deals another set of complexity – the real business/functional complexity, and here it should be limited to that.

Nope – the power of Generics has not come so far.

It comes if form of performance, better designing and type safety and blah...blah...

Assume you are walking on a river bridge footpath. It’s easy… but just imagines that without a railing… ok-ok you are smart and you don’t see a problem there – now imagine your ageing parents or your kids are walking there… Hmm - I am sure you will see a problem there now – yes the problem is there because all aren’t smart and most importantly you can’t get confident about smartness of others. So the safety is required and the railing is really necessary. Forget about walking – your kid will run on that and you will still be ok. So Type-Safety provided by .NET is really an important feature.

We deal a lot with collections/arrays in general programming. For that matter the database is also a sort of collections only. A table is collection of rows. A row is collection of cells and likes. Excel sheet is another good example.

.NET 1.0 offered collections like ArrayList and Hashtable and Stack – these deals with objects. As object is base for anything so virtually you can insert, push, add any type in these collections. In normal scenario we rarely need this – what we need is collection of a particular type. For example – a collection of Controls, Datarows, Forecasts, etc. As a developer we know what we are going to store in a collection and what type of object we are going to read. But even if we know it the framework still requires casting because framework is not aware of the object type stored in an Arraylist or Hashtable.

Continuing with the code

C#
ArrayList a = new  ArrayList();
a.Add(new Person());
Person item = (Person)a[0]; // Casting is required

List<T> essentially provides the same feature – with one added thing. It is not bound to Object – instead the type can be supplied by developer and so the overhead of casting is avoided.

C#
List<Person> l = new List<Person>();
l.Add(new Person());
Person itm = l[0]; // Casting not required

For performance perspective it’s a big benefit – and the code is also neater.

The other benefit is type safety. In the Arraylist you could add any object but with list you can add only object of type with which it has created. [ or of a derived type ].

C#
public class Politician : Person
{
      public string Speciality = "Nothing";
}

l.Add(new Politician()); // This is valid but while reading it has to be of original type only
Politician sp = l[1]; // invalid operation
Person sp = l[1]; // valid one

The LINQ library created by framework team depends a lot on Generic Interfaces. The full library is set of Extension methods that are created on Interfaces – like IEnumerable<T>

C#
var hni = l.Where(mi => mi.NetWorth > 5000);

The Where is part of LINQ library that takes a function that will accept an argument of type Person and will return true or false. The where will internally iterate in the IEnumerable<person> collection and call this method passing the current item in the argument. If that returns true - that will become part of hni. This is not the exact way how it happens but something very similar. Here we are passing a lambda expression instead of a function.

This is about consuming a Generic Type – we have so many like List, Dictionary, IEnumerable, and so on..; now what about creating a Generic class for our use: - consider following not so ideal example

C#
public interface IDTO { }

public interface IModel {
  IDTO GetData();
  void Persist(IDTO dataObject);
}

public interface IView {
  void BindData(IDTO datasource);
  void SetUpdateEvent(Action<IDTO> updateAction);
}

public abstract class Controller<V, M> where V : IView, new()
                                   where M : IModel, new() 
{
 V view;
 M model;
 public Controller() {
       view = new V();
      model = new M();
 }
 public void Load() {
       var datasource = model.GetData();
      view.BindData(datasource);
      view.SetUpdateEvent(OnUpdate);
  }

 public void OnUpdate(IDTO dataSource) {
      model.Persist(dataSource);
      var datasource = model.GetData();
      view.BindData(datasource);
 }
}
public class MVC<M, V, C> where M : IModel
                          where V : IView
                          where C : Controller<V, M>, new()
{
  C controller = null;
  public void Load()
  {
      controller = new C();
      controller.Load();
  }
}

We are simulating a pattern here – roughly. The main thing here is the Controller class which is a generic one and relies on two types V and M.

There are few constraints we have put with where clause. If we don’t put it there then we can’t write code like

C#
model.GetData();

Because the Controller class will be unaware of the type of M. By default it will treat that as object - so no specific methods would be available there. By putting constraints we say that the M has to be IModel and it must have a default constructor [the new() constraint]. And this opens up a lot of space to code a framework. This was not so with C++ templates

Now the compatibility test

C#
public class Politician : Person
{
 public string Speciality = "Nothing";
}

public class Commonman : Person
{
 public string Occupation = "self employed";
}

Do the following now. It will pop names of Politicians

C#
List<Politician> ms = new List<Politician>();
foreach (Person p in ms) 
{
    MessageBox.Show(p.Name); 
}

We are iterating in the Politicians list as its base type that is Person. It’s allowed – But consider following

C#
List<Politician> ms = new List<Politician>();
foreach (Person p in ms) 
{
  MessageBox.Show(p.Name);
  p = new Commonman(); // not allowed
}

At other places p being a Person Type can have an object of type Commanman – but here in case it is not allowed. Because it can cause potential damage to the actual underlying list that will not accept a Commanman as a Politician and system will throw an exception at runtime

To prevent this runtime behavior the framework doesn’t allow the write operation here and so it goes well – no problem. In other words the List<T> within a foreach loop is treated as IEnumerable<out T> here T is the type we define in the foreach loop for reading current-item. [ this is known as Co-Variant ]. You can get the value but you can’t put in that list.

C#
Action<Person> highlight = p => Console.WriteLine(p.Name); 

Now in the above example we are defining an Action on base type Person. If we do a casting like following

C#
Action<Commonman> hcman = highlight;  

It’s allowed because Action is Contra-Variant. Meaning the operation that is valid on Person can be applicable on any derived type be it Commanman or Politician. This is so because Action represents a void delegate – so the applicable Type will be supplied as a parameter – meaning at place of Base a Derived type can be supplied.

There are many more points to discuss.. I will keep those for my future posts...> 

License

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


Written By
Technical Lead Thomson Reuters
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMy vote of 1. Pin
FatCatProgrammer19-Feb-13 8:09
FatCatProgrammer19-Feb-13 8:09 
GeneralMy vote of 2 Pin
Andreas Gieriet19-Feb-13 5:47
professionalAndreas Gieriet19-Feb-13 5:47 
GeneralMy vote of 5 Pin
Dave Kerr19-Feb-13 4:12
mentorDave Kerr19-Feb-13 4:12 

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.