Sometimes, programmers find themselves in need of adding new functionality to already existing codes, in order to improve or complete them. If the said source code is available, the task is simple – they only need to add the required functionality and recompile. However, they will often encounter situations when the code itself is not available, such as when they are using a referenced assembly (.dll or .exe file). In this case, in order to achieve their goals, they have a few options available. One of them is inheritance, when they can simply inherit the class they need to extend, and add the required functionality in the derived class. This has the major disadvantage of being difficult to apply, due to the fact that they would have to change all the instances of the base class with instances of the derived class. Aside of this, there is always the danger that the class they want to inherit is marked as sealed, which means it cannot be inherited.
The second option available is extension methods. They allow us to extend existing types (class, structure or interface) without the need to change their codes or to use inheritance, even when the existing types are marked as sealed.
Extension methods must always be declared as static, and they can only be declared inside static classes. But, to better visualize them, let’s take a simple example where they might be useful. Let’s say we have these codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; namespace HelloWorld { public class Program { static void Main() { DateTime date = DateTime.Parse("5/5/2020"); DateTime time = DateTime.Parse("1/1/0001 18:15pm"); Console.WriteLine(date); Console.WriteLine(time); Console.Read(); } } } |
We have two simple DateTime instances, date and time, and we print them to the console. You can notice that in the case of date, since we are not declaring any time part, it will have the default value of 12:00 AM, like this:
Now, suppose we want to combine the two objects, so that we end up with a DateTime instance that contains the date part of date and the time part of time. One way we can do this is by creating a new DateTime instance, and assign its values by using date and time members:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System; namespace HelloWorld { public class Program { static void Main() { DateTime date = DateTime.Parse("5/5/2020"); DateTime time = DateTime.Parse("1/1/0001 18:15pm"); DateTime combined = new DateTime( date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second); Console.WriteLine(combined); Console.Read(); } } } |
In case you need this functionality in a lot of places, we can obviously create a separate method that returns this functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; namespace HelloWorld { public class Program { static DateTime Combine(DateTime datePart, DateTime timePart) { return new DateTime( datePart.Year, datePart.Month, datePart.Day, timePart.Hour, timePart.Minute, timePart.Second); } static void Main() { DateTime date = DateTime.Parse("5/5/2020"); DateTime time = DateTime.Parse("1/1/0001 18:15pm"); DateTime combined = Combine(date, time); Console.WriteLine(combined); Console.Read(); } } } |
But, I don’t know about you, but if you are a little bit more like me, you’d find it even more logical to have it in this form:
1 |
DateTime combinedVariant = date.Combine(time); |
Now, obviously, this gives as an error, since DateTime does not have a Combine() method that we can use:
Again, syntactically, this is just as pleasing: “hey, date, why don’t you combine with time?”, so they are both the same. However, I personally prefer the latter.
At this point, in order to get rid of that error, I already explained we have two options: inheritance or extension methods. But, if we go to the definition of DateTime, we notice that it is a struct:
You may not know, but structures are implicitly declared as sealed, so, this rules out inheritance from start. Therefor, let’s implement the only remaining variant, an extension method.
As I said, extension methods can only live inside static classes, so we need to mark our Program class as static. I also said that extension methods are always static, so we need to mark our Combine() method as static too. Finally, the last thing we need to do in order to inform the compiler that our Combine() method is an extension method, and not just any regular method, is to use the this keyword in front of its first parameter:
1 2 3 4 5 6 7 8 9 10 |
static DateTime Combine(this DateTime datePart, DateTime timePart) { return new DateTime( datePart.Year, datePart.Month, datePart.Day, timePart.Hour, timePart.Minute, timePart.Second); } |
The reason we are using this in front of the first parameter is to indicate to the compiler the type we want to extend with an extension method. In other words, when we use this DateTime, the compiler understands that our extension method is a method that will be added to a DateTime type. If we used this string, we would have created an extension method for the string class, and so on. Notice that the this keyword must always be used in front of the first parameter of the extension method, and not on subsequent parameters.
At this point, you will notice that the error in Visual Studio is gone. This means two things: first, it appears that our date instance of DateTime DOES contain a method named Combine(), which we can call by supplying a single parameter, time:
1 |
DateTime combinedVariant = date.Combine(time); |
But, wait! Didn’t we declare our Combine() extension method with TWO parameters? Why isn’t the compiler complaining about using only one of them? The reason for this is that since we used the this keyword, and the compiler knows this is an extension method, it implicitly understands that if we use this method on a DateTime object (date, in our example), the first parameter, the this DateTime one, is the same as the one on which the extension method is used upon. In other words, if we use date.Combine(), the compiler implicitly knows that the this DateTime parameter is the same as date, because we are calling the Combine() method on the date object.
The second thing we get from this is that we can use extension methods by calling them explicitly, as we do here:
1 |
DateTime combined = Combine(date, time); |
In this case, since we are not calling the Combine() method on a DateTime instance, we are forced to specify both the first and the second parameters, so that the compiler understands on which DateTime object should it combine the time to. However, since this is an extension method, it is unnatural to use it in this way, and it is not considered a good practice.
The thing to take from here is that extension methods are just normal static methods, and can be used as static methods, but they have the advantage of also allowing us to use them as instance methods on the types that they extend.
In fact, at MSIL (Microsoft Intermediate Language) level, extension methods are not valid code. The compiler simply “cuts” the instance on which the extension method is used and paste it as the first parameter of the static method call, effectively converting it to a normal static method call.
Notice that trough extension methods we can add “implemented methods” even to interfaces. Of course, by this time we all know that interfaces cannot contain functionality, they are used only to define members, properties or methods signatures. Not entirely true. Extension methods can also extend interfaces, in which case they “add” functionality to an interface, in the same way they do on concrete types. So, if any wiseguy employer asks you if interfaces can contain functionality, offer a wiseguy response, and reply that they can, only through extension methods.
Extension methods also have a few caveats. One of them is the fact that, obviously, they cannot access the private members of the types they extend. Another is the fact that programmers can get to a point where they have a ton of extension methods, just to try and avoid inheritance. Personally, as a professional programmer, I like to use extension methods here and there, just keeping them as a nice tool to have in the toolbox. But the place where these extension methods really shine is LINQ (Language Integrated Query), of which we will learn in the following lessons.
Tags: extension methods, linq, methods, static members