Understanding Extension Methods and Mixin
C# extension methods and Mixin implementation.
Introduction
Extension methods is a powerful concept. We will explore the nuances of extension methods in this article. This article will also look at the Mixin implementation in C#.
Background
An extension method is a static method defined in a non-generic static class, and can be invoked using an instance method syntax. Let’s look at the sample code. Shown here is a very common scenario – evaluate an expression if value is greater than 0.
int NumOne = 10;
if (NumOne > 0)
Console.WriteLine("Number : {0} is greater than 0", NumOne);
Now, this evaluation can be abstracted out to a static method, as follows:
static bool StaticIsGreaterThanZero(int Number)
{
return Number > 0 ? true : false;
}
And, we can revise our if
condition as:
if (StaticIsGreaterThanZero(NumOne))
{
Console.WriteLine("Number : {0} is greater than 0", NumOne);
}
By doing this, we don’t have to write an if
condition all over our code. Anywhere in the program where we need a logic like if(Number > 0)
, we can use the StaticIsGreaterThanZero
method. In fact, we can promote this method in a separate static class, and use it all over the application. Methods like StaticIsGreaterThanZero
are known as helper methods. They add value by centralizing common logic and reducing code duplication / repetition. We all have seen code like this in many applications. Here is the twist – what if we want to make this static method part of the C# integer type? We want to use this static method as an instance method. Let's first code, and then we will evaluate the sample.
Step 1
public static class ExtensionMethods
{
public static bool IsGreaterThanZero(this int Number)
{
return Number > 0 ? true : false;
}
}
Step 2
if (NumOne.IsGreaterThanZero())
{
Console.WriteLine("Number : {0} is greater than 0", NumOne);
}
In step 1, we defined a public static class ExtensionMethods
and a static method IsGreaterThanZero
. Notice, the IsGreaterThanZero
method is very much like the StaticIsGreaterThanZero
method in terms of code, functionality, and structure. The only difference is the this
keyword in the method signature. Extension methods can be created by adding the this
keyword in front of the first parameter. IsGreaterThanZero
is an extension method. It extends the native int
type. That’s why we can write code like NumOne.IsGreaterThanZero()
. Extension methods can be used as instance methods. Extension methods will show up in Visual Studio Intellisense, as shown in Figure 1.
What if you want to extend custom types? It is possible to extend existing custom types without changing any code in the underlying type definitions. Here is a sample City
type:
public class City
{
private string _name;
private string _state;
public City(string name, string state)
{
this._name = name;
this._state = state;
}
public string Name
{
get
{
return _name;
}
}
public string State
{
get
{
return _state;
}
}
}
We want to extend the City
type and add a method to evaluate the city state. This method should return true
if the city is in California.
public static bool IsCityinCalifornia(City c)
{
return c.State == "CA" ? true : false;
}
We can add this method in the class, make it a public
method, and it will be available as an instance method. Alternatively, we can write IsCityinCalifornia
as an extension method without changing the City
class.
public static class ExtendCity
{
public static bool IsCityinCalifornia(this City c)
{
return c.State == "CA" ? true : false;
}
}
The IsCityinCalifornia
extension method can be used in our evaluation logic as:
City c = new City("Santa Ana", "CA");
City c1 = new City("Bloomington", "IN");
List<city> cities = new List<city>();
cities.Add(c);
cities.Add(c1);
foreach (City _city in cities)
{
//IsCityinCalifornia() is an extension method
if (_city.IsCityinCalifornia())
{
Console.WriteLine("City Name : {0} and City State : {1}",
_city.Name, _city.State);
}
}
Extension methods should be defined in a non-generic, static class. Otherwise, we will get a compile time error. That’s the reason for using a public static class ExtendCity
. If an extension method is defined in a different namespace, that namespace can be imported through using the namespace syntax [for example, using ABC.ExtensionMethod;
]. What will happen if we add the IsCityinCalifornia
method as an instance method in the City
type definition in addition to the extension method? As per the documentation, the instance method takes precedence over an extension method. So, the IsCityinCalifornia
instance method will be used.
Mixin: Extend the type by extending the interface. A good explanation on mixin in C# can be found on this blog post. Extension methods can be used to implement mixin in C#. Let's look at the code sample.
interface IPerson
{
int age { get; set; }
String name { get; set; }
}
class Person : IPerson
{
private int _age;
private string _name;
#region IPerson Members
public int age
{
get
{
return _age;
}
set
{
if (value > 0)
{
_age = value;
}
}
}
public string name
{
get
{
return _name;
}
set
{
if (!string.IsNullOrEmpty(value))
{
_name = value;
}
else
{
_name = "Unknown";
}
}
}
#endregion
}
static class ExtendIPerson
{
public static bool CanDrive(this IPerson testPerson, bool isDriver)
{
return isDriver;
}
}
static void Main(string[] args)
{
IPerson ip = new Person();
ip.name = "ABC";
ip.age = 21;
if(ip.CanDrive(true))
{
Console.WriteLine(ip.name +" can drive");
}
}
The Person
type is extended by extending the IPerson
interface. An extension method CanDrive
is implemented in the ExtendIPerson
static class. This is mixin in C#. Let's summarize the strengths and weaknesses of an extension method. First, weaknesses:
- Extension methods will be available as instance methods to all the instances of the type in a given assembly. For example, if you extend the native integer type, every variable of
int
type will be able to access the extension method as an instance method. - Extension methods defined locally in any particular application may cause the type to get out of sync with its original definition. Let's say, if we have a database type
dbconnection
in our application framework and this application framework is used in multiple applications. We extend thedbconnection
in only one application using an extension method. As a result,dbconnection
in one application will have more methods as compared to its original definition. This might be confusing for many end users. - Extension methods are less discoverable as compared to instance methods. For example,
Person
is a base class for theTeacher
class. Add aCanDrive
instance method in thePerson
class and add an extension methodCanDrive
on theTeacher
class. An extension method on theTeacher
class will never be used. TheCanDrive
instance method on thePerson
class is preferred by the compiler. - Overuse of extension methods might result in code duplication and maintenance nightmare.
On the positive front, extension methods are the only way to extend third party assemblies / APIs.
- Let's say we have a third party SLL and want to extend a particular type in it. But we don’t have access to the code. Tools like Reflector or Object Browser can be used to probe the DLL internals. Extension methods can be used in this scenario to extend the type in that DLL.
- Extension methods are central to LINQ implementation. Standard query operators in
System.Linq
are implemented as static extension methods. - Extension methods are very useful in extending
sealed
classes. - Mixin in C# can be implemented using an extension method.
As the documentation says, use extension methods “sparingly”. Extension methods can be used in certain cases where no other solution / trick will work. There are many good examples of extension methods in the .NET framework.
Points of Interest
Good articles on Extension methods and Mixin:
History
Original article.