Using Generic Extension Methods





3.00/5 (18 votes)
Explains the concepts of Generics (introduced in C# 2.0) and Extension Methods (introduced in C# 3.0) and how to mix those together to get a new concept of Generic Extension Methods that will make a difference in our class designs.
Introduction
In this article, I will explain the concepts of Generics (introduced in C# 2.0) and Extension Methods (introduced in C# 3.0), and how to mix those together to get a new concept of Generic Extension Methods that will make a difference in our class designs.
Background
This article talks about two concepts merged together: Generics and Extension Methods, so in order to understand it, you got to be familiar with both concepts, so I am going to summarize each of them.
Generics is a new feature in C# 2.0 and the Common Language Runtime, which introduces the concept of type parameters, so you can just specify your type for a class or a method at runtime.
Extension Methods is a new feature in C# 3.0 and the Common Language Runtime, which allows existing classes to use extra methods (extension methods) without being implemented in the same class or obtained by inheritance.
Generics Sample
Below is a sample of using Generics with methods; the same concept apply to classes, but for the purposes of this article, we are going to use a generic method:
public class SomeClass
{
///Using of one type as parameter
public string Method1<TInput>(TInput obj1)
{
//return a serialized object as string for example
}
///Using of two types as parameters
public TReturn Method2<TReturn, TInput>(TInput obj1)
{
//map data from an instance of TInput type
//to TReturn type and return an
//instance of TReturn type
}
///Using of three types as parameters
public TReturn Method3<TReturn, TInput1, TInput2>(TInput1 obj1, TInput2 obj2)
{
//Map data from an instance of TInput1 and TInput2 types to an
//instance of TReturn type and return that instance
}
}
Fairly simple. For a certain method, you declare types before the ()
and use them as your input or output or both,
or even inside the method body without being used as input or output.
Extension Methods Sample
Below is a sample of using Extension Methods:
public class MyClass{
private string m_Name;
public string Name{
get{return m_Name;}
set{m_Name=value;}
}
}
public ExtensionMethodsClass{
public static string DoSerialize(this MyClass entity){
//Serialze the entity object and return its string represenatation
}
}
Note the use of the this
keyword in the DoSerialize
method. This indicates that an instance of MyClass
will only be able to call this method like this:
MyClass instance=new MyClass();
instance.DoSerialize();
Although the DoSerialize
method is not a member of MyClass
, you can just call it like it is a member!
The input parameter of the DoSerialize
method, the this
keyword, tells us
that an instance of type MyClass
will call DoSerialize
as it is a member in it.
Pretty ... ha ! Now, let's take a look at how to merge the two concepts together, it's fabulous actually ;)
Generic Extension Method
As we have seen earlier, the this
keyword will determine which type will call the extension method as it's a member.
Let's replace the type of the this
keyword (MyClass
) with the Generic Type T
, as follows:
public ExtensionMethodsClass{
public static string DoSerialize<T>(this T entity){
//Serialze the entity object and return its string represenatation
}
}
Up to here, any type will use this extension method to make a serialization using the DoSerialize
method, which is good and bad
at the same time! Good because we are generic in our types and a lot of types will use this method and there is no need to implement the same extension method
over and over again; bad because it will receive any type! What if we pass a non-serializable type!?? Oops ... I think this will make a problem for us that we should take care of!
Using the 'Where' Clause with Generics
Here comes the role of the where
clause. The where
clause is used to determine the type used as generic, more likely
the specifications of the type we are going to use as generic. We can determine if it should have a default constructor too, or the interfaces
it should be implementing, and focus on that, because it is the key to a good implementation as we would see at the end of this article.
No further explanation, let's take a look at the code sample below:
public ExtensionMethodsClass{
public static string DoSerialize<T>(this T entity) where T: MyClass{
//Serialze the entity object and return its string represenatation
}
}
Using the above code, we determine that the generic type used should be of type MyClass
or inherited from MyClass
;
seems good, but actually, it is not so far! Because, by determining the type to be MyClass
, we are not much different from our first implementation using:
public static string DoSerialize(this MyClass entity){ ...}
which we wanted to expand from the whole beginning! So, how can we get it all together ?!
When and How to Use it ? And the Effect on Design
Usually, we have an application, we have a lot of business classes in it, and most probably, we have that base class or interface for all these business classes and common behaviours are replaced within that class or interface. Now, these behaviours should be only implemented for our business entities, and not any other .NET classes, for example. We have here two options:
- Place these behaviours in the base class or interface and all entities will inherit it (what is usually done).
- Make a generic extension method where the type is the base class or interface (a new way of doing it).
Both solutions will do the trick for you, but it will affect your design on the long run; you see, inheritance always ties our code with its limitations. It is not that this is a bad thing; it is just the way inheritance works; and sometimes, solving problems using inheritance will lead to major problems in the design and then to maintenance problems in certain cases. So, you have to be careful doing so. On the other hand, Generic Extension Methods will make us avoid those limitations: by inheritance, because it is expandable. Let's take a look at the following code which will illustrate a good example of Generic Extension Methods and help in the design as well:
public class BaseClass{
}
public class MyClass: BaseClass, ISerializable{
}
public class YourClass: BaseClass, ISerializable{
}
public class HisClass: ISerializable{
}
public class HerClass: ISerializable{
}
public ExtensionMethodsClass{
public static string DoSerialize<T>(this T entity) where T: BaseClass{
//Serialze the entity object and return its string represenatation
}
}
As we can see in the above code, we have four ordinary classes and an Extension Methods class; the four classes are serializable,
the classes MyClass
and YourClass
will have the ability to use the Extension Method DoSerialize
,
because they inherit from the BaseClass
class; on the contrary, the classes HisClass
and HerClass
will not have that option.
But in this context, a member here at CodeProject noted how using:
public static string DoSerialize<T>(this T entity) where T: BaseClass { ...}
will be different from using :
public static string DoSerialize(this BaseClass entity){ ...}
Actually, in this context, it won't be much of a difference... but, let's take a look at the flexibility we could have by using the following example:
//Our Interfaces
public interface IFirstInterface
{
}
public interface ISecondInterface
{
}
//Our Classes
public class AClass : IFirstInterface
{
}
public class BClass : ISecondInterface
{
}
public class CClass: IFirstInterface , ISecondInterface
{
}
//Our Extension Methods Class
public static class ExtensionMethodsClass
{
public static void DoSerialize<T>(this T entity)
where T: IFirstInterface, ISecondInterface
{
}
}
I think the code is obvious enough to explain itself :). Simply an instance of CClass
will only be able to call the Extension Method
DoSerialize()
, and neither AClass
nor BClass
:
AClass A=new AClass();
BClass B=new BClass();
CClass C=new CClass();
A.DoSerialize();//Compiler Error
B.DoSerialize();//Compiler Error
C.DoSerialize();//Works Fine
Conclusion
Generics and Extension Methods are two great concepts which really help in our design of classes; they help us in minimizing our code and use the concept of Code Refactoring. In addition, maintenance will be always much easier using Extension Methods. But, I think we should always keep an eye on the fact that our design demands will control what technologies we should use, not the other way around. Understanding the technologies we use will help us more in taking the major decisions in our designs!
History
- Created the article: September 4th, 2008
- Updated the article: September 5th, 2008