Generics
For years, I have tried to learn more about generics, but since my work was never in need for such implementation, I never bothered. Finally, an opportunity arose. I cannot go in specific details, but I had to write a Windows service (in C#) that daily obtains vast amount of similar data which must then be written to half a dozen tables of exactly the same structure. At this point, I would like to add, that amount of data obtained and amount of destination database tables are due to change at any point in future.
What are Generics?
According to Microsoft MSDN site “Generics allow you to define type-safe classes without compromising type safety, performance, or productivity. You implement the server only once as a generic server, while at the same time you can declare and use it with any type”. In reality this translates to the fact that if you must implement a class or function that needs to do same thing for different types, you can only write it once.
In .NET world, most of us use Generics daily, without knowing it. They are disguised as lists, collections and dictionaries.
Basic use
Generics are best explained by example. Imagine that you need to create a simple calculator. This calculator must be able to perform operations on various types (e.g. integer, double, decimal, long etc.). One way would be to write an interface and then implement class once per type, but that means each correction made, must be made in multiple different spots. Easier way is to use Generics and implement a class like:
public class Calculator<T> { public T Add(T value1, T value 2) { return value1 + value2; } }
You can then simply use this class in your code as:
Calculator<int> calcInt = new Calculator<int>(); int nSum = calcInt.Add(10, 5);
Another implementation can be done in case of such a simple calculator. This time class will not use Generics, but method Add will:
public class Calculator { public T Add<T>(T value1, T value 2) { return value1 + value2; } }
In your program, you then need to do:
Calculator calc = new Calculator(); int nSum = calc.Add<int>(10, 5);
This brings you better scalability, as you can also do the following without initializing another Calculator object instance:
double dSum = calc.Add<double>(10.0, 5.0);
Limiting acceptable types
In previous example, we assumed that a user would only use types that support operator ‘+’ as values for our Calculator. However, nothing prevents user form using a type that doesn’t. Unfortunately for you, Generics cannot be limited to primitive types. There is an ongoing debate on StackOverflow and also some workarounds that exceed intention and topic of this article.
In my real life problem, I had to write a Generic method that only accepts type that derive from EntityObject class. Here is how one would go about doing this using where statement in method definition:
private void SaveInterestRate<T>( params ) where T : EntityObject { ... }
Interesting thing is that you can also limit into types to all classes or all structs by simply doing
private void myFunction<T>() where T : struct {}
or
private void myFunction<T>() where T : class {}
respectively.
Creating new variable of type T
In some cases a generic method will need to create an object of type that was passed to generic function and then return it. In your method, you can do this:
private T myFunction<T>() where T : class { ... T myObject = (T)Activator.CreateInstance( typeof( T ) ); return T; }
Bare in mind that where statement is mandatory or your code will throw NotSupportedException. You can also pass constructor parameters to type like this:
T myObject = (T)Activator.CreateInstance( typeof( T ), new object[] { "test", 10 } );
Accessing and setting class properties is where things get tricky(er). You need to obtain property of a type and then get value from generic variable. Assuming T type has property Date…:
private T myFunction<T>() where T : class { ... T myObject = (T)Activator.CreateInstance( typeof( T ) ); Type typeMyObject = typeof( T ); typeMyObject.GetProperty("Date").SetValue( myObject, DateTime.Now, null ); DateTime date = Convert.ToDateTime( typeMyObject.GetProperty("Date").GetValue( myObject, null ) ); return T; }
Using LINQ with Generics
Things get even trickier when using LINQ. Let’s say we have a class called InterestRate, which has three properties Type, Date and Value. You need to create a method that returns last date for specified type using LINQ. This would be how one could go about when there is only one type to which method should apply:
private DateTime GetLastDate( List<InterestRate> lstRates, int nType ) { DateTime dateLastEntry = DateTime.Now; var dates = from rate in lstRates where lstRates.Type = nType orderby date.DATUM descending select date.DATUM; if (dates.Any()) { dateLastEntry = dates.First(); } return dateLastEntry; }
However, the problem occurs when you have multiple types, similar to InterestRate, which could use the same method. Obviously strongly typed LINQ is out of the question, as you cannot address T.Type property directly. Here is where dynamic LINQ steps in. But first, let’s do a modification and assume that all classes similar to InterestRate and InterestRate class as well implement IRate interface with properties Type, Date and Value. A generic function will then look like:
private DateTime GetLastDate<T>( List<T> lstRates, int nType ) where T : IRate { DateTime dateLastEntry = DateTime.Now; string strWhere = string.Format( "it.Type == {0}", nType ); var dates = lstRates.Where( strWhere ).Select( "it.Date" ) .OrderBy( "it.Date DESC" ); if (dates.Any()) { dateLastEntry = dates.First().GetDateTime( 0 ); } return dateLastEntry; }
There are several things you should be aware of:
- Prefix to property name ‘it.’ is mandatory, or you will get an OperationException explaining that property cannot be found in current context.
- Where clause, for some reason, must be stored in a string prior use (if passed as string; you can also use Expression).
- OrderBy statement must be called last, or your result will not be sorted.
- You need to use GetDateTime or similar function to obtain result in type that you want. Otherwise a type of DbDataRecord is returned.
Conclusion
Generics are definitely fun to work with. Be minded though that extensive use of Generics will make your code difficult to read for anyone not familiar with the concept.
Leave a Reply