When you read the word extension. What is the first thing that comes to your mind? Probably it will be Chrome or Firefox plugin that alters (aka. extends) the ways web browser works. Plus, we are going for the extensions because we can’t change the code of a web browser by our own. This bears a resemblance to the extensions we can create in programming. Let’s see how.
If you want to download the code, it can be found at: https://github.com/Nivo1985/AdvancedCSharp. The project names match the blog post topic.
What, why and when?
First what. I’m going to use definition provided by the Microsoft. Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type. For client code written in C#, F# and Visual Basic, there's no apparent difference between calling an extension method and the methods defined in a type.
Now why. We should be using extension methods if we want to extend to behaviour of a type. And we believe that the best place for method in development is directly inside the type.
Finally, when. We should do it when we don’t have direct access to the type we need to extend. Although, we can extend the typed we are developing, there is no gain in that. On the other hand, the side effects of that can be unintentional code obfuscation. And we don’t want that 😉
Basic usage
We will start simple. By extending the types we are well familiar with. The place for storing the extension method will be BlogExtensions.cs file. Now the class looks like this.
1 2 3 4 5 6 7 8 9 10
public static class BlogExtensions { public static string InsertSpaces(this string s) { return s.ToCharArray().Aggregate("", (current, c) => current + $"{c}_"); } public static string SumWith(this int x, int y) { return $"Sum of {x} and {y} equals {x + y}";
There are three key elements required for extension method. One, it needs to be placed in public static class. Two: it needs to be public static itself. Finally: the first parameter of the extending method needs to be this and type that we are extending.
In the example above we can see two extension methods being defined. First one is called InsertSpaces. It is an extension of type string. It operates on a string it has been called on and returns a string with underscores inserted after each original character.
The second method SumWith extends integer. In this case the extension method takes one extra parameter and returns a string. Running the code that uses the extension methods doesn’t differ from calling any other method. We can test these two new methods in Program.cs file. The code for test looks like this and give us these results.
1 2 3 4 5 6
using Extensions; Console.WriteLine("Karol".InsertSpaces()); Console.WriteLine(6.SumWith(3)); Console.ReadKey();
The results shouldn’t be a surprise. Both of developed extension methods worked in the way we wanted. Now to something more complex with extending the custom class.
Class extension
To extend the class we need to have a class. Shocker, I know. The class we will be working with looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
namespace Extensions; public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public string GetIntroductionPublic() { return $"PUBLIC INNER - Hello from: {FirstName} {LastName}."; } private string GetIntroductionPrivate() { return $"PRIVATE INNER - Hello from: {FirstName} {LastName}."; } }
This is a very simple class. It has only two properties, a constructor and two methods. The only goal of this methods is to demonstrate how they will work with extension methods. That being said let’s add three extension methods. To do that we will stick with the BlogExtensions class. Now, the class looks like so.
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
namespace Extensions; public static class BlogExtensions { public static string InsertSpaces(this string s) { return s.ToCharArray().Aggregate("", (current, c) => current + $"{c}_"); } public static string SumWith(this int x, int y) { return $"Sum of {x} and {y} equals {x + y}"; } public static string GetIntroduction(this Person person) { return $"EXT - Hello from: {person.FirstName} {person.LastName}."; } public static string GetIntroductionPublic(this Person person) { return $"PUBLIC EXT - Hello from: {person.FirstName} {person.LastName}."; } public static string GetIntroductionPrivate(this Person person) { return $"PRIVATE EXT - Hello from: {person.FirstName} {person.LastName}."; } }
There are three extension methods extending the Person class. They look very similar but there is a difference in a way they fit into class design. GetIntroduction is a new method in the class design. GetIntroductionPublic is an extension method with the same name as the class inner public method. GetIntroductionPrivate is an extension method with the same name as the class inner private method. No we will run all on this three methods and see what will happen. The code to run them looks like this and produces this results.
1 2 3 4 5
var person = new Person("Karol", "Rogowski"); Console.WriteLine(person.GetIntroduction()); Console.WriteLine(person.GetIntroductionPublic()); Console.WriteLine(person.GetIntroductionPrivate());
The results are giving us a good insight into how the process works. In first case the method from the extension class was called. Sure, it was, there was no other option to choose. In second case the inner public method was executed. In the third case we still can’t use class private method so the public extension method is the one that is being used. Those three examples are worth remembering if you plan on using extension methods in your code.
It is a jungle out there
At this point you should be asking yourself a question. Are extension methods being used in .Net ecosystem. Before I answer this question, please look at this piece of code.
1 2 3 4 5 6
var people = new List<Person>(); people.Add(new Person("Karol","Rogowski")); people.Add(new Person("Adam","Korcz")); people.Add(new Person("Kamil","Tuk")); Console.WriteLine(people.FirstOrDefault(new Person("Empty","Empty")).GetIntroduction());
In this super simple example, we are taking first or default element from the list of Person class objects and call GetIntroduction on this object. The interesting thing about this example is the fact that FirstOrDefault is an extension method itself. You can check that is you go to the definition of this method. If you do so you will see this piece of code.
1 2 3 4 5 6 7 8 9 10 11
/// <summary>Returns the first element of a sequence, or a default value if the sequence contains no elements.</summary> /// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam> /// <param name="source">The <see cref="IEnumerable{T}" /> to return the first element of.</param> /// <param name="defaultValue">The default value to return if the sequence is empty.</param> /// <returns><paramref name="defaultValue" /> if <paramref name="source" /> is empty; otherwise, the first element in <paramref name="source" />.</returns> /// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception> public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue) { TSource? first = source.TryGetFirst(out bool found); return found ? first! : defaultValue; }
And wouldn’t you know. FirstOrDefault is an extension method. That answers the question of extension methods real world usage. Extension methods are being used heavily in .NET environment. Very often in Nuget packages.
The code above has one more element that I would like to dive deep into. That is generic code in extension methods.
Generic extension methods
As you have seen above, we can create extension method that uses generic types. So, let’s modify BlogExtensions class for the last time. Now the class looks like so.
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 31
public static class BlogExtensions { public static string InsertSpaces(this string s) { return s.ToCharArray().Aggregate("", (current, c) => current + $"{c}_"); } public static string SumWith(this int x, int y) { return $"Sum of {x} and {y} equals {x + y}"; } public static string GetIntroduction(this Person person) { return $"EXT - Hello from: {person.FirstName} {person.LastName}."; } public static string GetIntroductionPublic(this Person person) { return $"PUBLIC EXT - Hello from: {person.FirstName} {person.LastName}."; } public static string GetIntroductionPrivate(this Person person) { return $"PRIVATE EXT - Hello from: {person.FirstName} {person.LastName}."; } public static IEnumerable<T> Search<T>(this IEnumerable<T> source, Func<T, bool> isMatch) { return source.Where(isMatch); } }
At the bottom of the code, we can see a newly added method of Search. It is a generic method that returns a generic IEnumerable. Methods second parameter in very interesting. The method expects a predicate. That predicate is to take the generic type as a parameter. Looks familiar? Good, it should to. It was written in LINQ fashion. Usage of this method is also very like usage of LINQ methods. Just take a look.
1 2 3 4 5 6 7 8 9
var people = new List<Person>(); people.Add(new Person("Karol","Rogowski")); people.Add(new Person("Adam","Korcz")); people.Add(new Person("Kamil","Tuk")); foreach (var person in people.Search(p => p.FirstName[0] == 'K')) { Console.WriteLine(person.GetIntroduction()); }
The usage of the new Search method just like any “other” LINQ method. That was our goal. Now you see how easy it is to extend the code.
Summary
This was the fourth article in the advanced c# series. We have talked about extending existing types. We have seen that this technic is being widely used and give us huge range of possibilities. But it needs to be used with cautions.
If you have any questions, please drop me a line at karol.rogowski@softwarehut.com.
Till next time. Keep coding.