DEV Community

Cover image for Extension Methods in C#
Ivan Kahl
Ivan Kahl

Posted on • Originally published at blog.ivankahl.com

Extension Methods in C#

Have you ever needed to extend a class provided by a third-party library? Perhaps you want to add additional functionality or simplify a particular set of methods in the library.

A common approach to solving this problem might be to create a class that inherits the third-party class and has the additional functionality you need.

public class ExtendedThirdPartyClass : ThirdPartyClass
{
    // Add my extra functionality here.
}
Enter fullscreen mode Exit fullscreen mode

While this is a potential solution, we will run into problems if the class we're trying to inherit from is sealed. This is because a sealed class prevents any other classes from being able to inherit it (read more here).

Our next solution could be to write a wrapper class and then add our additional functionality there. This would look something like this:

public class ExtendedThirdPartyClass
{
     private ThirdPartyClass _underlyingClass;

     // Add method from ThirdPartyClass here which forward the
     // calls to _underlyingClass;

     // Add my extra functionality here.
}
Enter fullscreen mode Exit fullscreen mode

This could work, but it's a lot of effort! We will have to rewrite each of the methods in ThirdPartyClass in our ExtendedThirdPartyClass and forward the method calls to our underlying ThirdPartyClass. While there are tools that can assist with this (ReSharper), it could result in a lot of unnecessary code and make it harder to see what functionality is part of ThirdPartyClass and what functionality is our own when looking at the ExtendedThirdPartyClass code. On top of that, we might also have to implement the underlying interfaces that ThirdPartyClass implements in our ExtendedThirdPartyClass which can also get messy.

I am by no means trying to bash either of the above approaches. There might be a use case that requires one of the above solutions in which case it would be better to use one of those. We are simply looking at a common problem and possible solutions for that problem so that we can provide context for another potential solution: Extension Methods!

Extension methods are methods that allow you to add extra functionality/methods to an existing type without having to inherit the type or write a wrapper around it. These methods give us a powerful way of writing additional functionality with minimal effort and code.

Structure of an extension method

Structure of an extension method

Let's look at three rules on how extension methods are structured before diving into some examples.

Extension methods live in static classes

Extension methods live in static classes

Extension methods can only be created inside static classes in .NET. This might seem a bit frustrating (especially if we already have another class with related logic), but it helps a lot! Having a separate class for extension methods helps with code organization and helps us clearly identify methods which are extension methods for other types.

Extension methods are static methods

Extension methods are static methods

Since extension methods aren't called on an instance of the class that they're in (this would never be possible since the class is static), we need to also make the method static.

The "this" parameter keyword

The "this" parameter keyword

The this keyword on the first parameter of the method is what makes this method an extension method. By adding the this keyword to the first parameter, we can now call this method on any object (whose type matches the type of the first parameter) as if this method is an instance method on that object.

When we call the extension method on an object, the object which we call the extension method on becomes the first parameter of the extension method. If this sounds a bit confusing, the diagram below might provide some clarity:

Diagram illustrating how an object is passed to an extension method.

Examples

We now have an idea of how an extension method is structured in C#. Let's move onto some examples!

IsIn string extension method

In this first example, let's write a .IsIn(...) extension method for a string that will check whether the specified string is equal to one of the strings in the parameter list. A boolean is returned indicating if the specified string is equal to one of the strings in the parameter list.

To do this, we will use the params keyword in our parameters. You can read more about this here.

StringExtensions.cs

public static class StringExtensions
{
    public static bool IsIn(this string s, params string[] options)
    {
        // Very simple... we just call the .Contains method on the
        // array of strings. 
        return options.Contains(s);
    }
}
Enter fullscreen mode Exit fullscreen mode

We can now call our extension method on any string. For example,

Program.cs

var day = "Monday";

Console.WriteLine(day.IsIn("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"));
// Output: True

Console.WriteLine(day.IsIn("Saturday", "Sunday"));
// Output: False
Enter fullscreen mode Exit fullscreen mode

Generic IsIn extension method

What if we wanted to be able to call our extension method on more than just strings. C# lets us use type parameters which allow us to create generic methods. The good news is that we can make use of generic types in extension methods as well!

IsInExtension.cs

public static class IsInExtension
{
    public static bool IsIn<T>(this T s, params T[] options)
    {
        return options.Contains(s);
    }
}
Enter fullscreen mode Exit fullscreen mode

We can now call our .IsIn(...) method on almost any type:

Program.cs

/*
 * Example 1: With a string
 */

var day = "Monday";

Console.WriteLine(day.IsIn("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"));
// Output: True

Console.WriteLine(day.IsIn("Saturday", "Sunday"));
// Output: False

/*
 * Example 2: With an enum
 */

var level = Levels.High;

Console.WriteLine(level.IsIn(Levels.High, Levels.SuperHigh));
// Output: True

Console.WriteLine(level.IsIn(Levels.SuperLow, Levels.Low));
// Output: False

enum Levels 
{
    SuperLow,
    Low,
    Medium,
    High,
    SuperHigh
};
Enter fullscreen mode Exit fullscreen mode

Look how neat and versatile that is! By writing a very simple extension method, we are able to write neater code. And by making it generic, we have a flexible method that we only need to write once and can then use with multiple types.

You'll notice that we don't specify the type explicity when calling the .IsIn<>(...) method. This is because .NET is able to determine the generic type based on the arguments provided to the method.

Bonus example: A fluent API for MailMessage

Have you ever worked with the System.Net.Mail API? If so, you've probably written code that might look something like this:

Program.cs

using System.Net.Mail;

var message = new MailMessage();

message.To.Add(new MailAddress("ivan@example.com", "Ivan Kahl"));
message.From = new MailAddress("system@example.com", "System")
message.Subject = "System Usage Notification";
message.Body = "<p>Hi there Ivan,</p><p>This is just a notification for you.</p>";
message.IsBodyHtml = true;

var smtpClient = new SmtpClient();
smtpClient.Send(message);
Enter fullscreen mode Exit fullscreen mode

That's okay, but what if we had a fluent API for MailMessage that let us chain methods together to build an email? Would that not look a bit neater? I think so!

Let's give it a shot. Here are the extension methods:

MailMessageExtensions.cs

using System.Net.Mail;

public static class MailMessageExtensions
{
    public static MailMessage AddTo(this MailMessage message, string address,
        string? name = null)
    {
        message.To.Add(name == null
            ? new MailAddress(address)
            : new MailAddress(address, name));

        return message;
    }

    public static MailMessage SetFrom(this MailMessage message, string address,
        string? name = null)
    {
        message.From = name == null
            ? new MailAddress(address)
            : new MailAddress(address, name);

        return message;
    }

    public static MailMessage SetSubject(this MailMessage message, string subject)
    {
        message.Subject = subject;
        return message;
    }

    public static MailMessage SetHtmlBody(this MailMessage message, string html)
    {
        message.Body = html;
        message.IsBodyHtml = true;
        return message;
    }

    public static MailMessage SetPlaintextBody(this MailMessage message, string plaintext)
    {
        message.Body = plaintext;
        message.IsBodyHtml = false;
        return message;
    }

    public static void Send(this MailMessage message, SmtpClient client)
    {
        client.Send(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

We can now construct that same email message using the same MailMessage object but with our extension methods instead! Take a look:

Program.cs

using System.Net.Mail;

var smtpClient = new SmtpClient();

new MailMessage()
    .AddTo("ivan@example.com", "Ivan Kahl")
    .SetFrom("system@example.com", "System")
    .SetSubject("System Usage Notification")
    .SetHtmlBody("<p>Hi there Ivan,</p><p>This is just a notification for you.</p>")
    .Send(smtpClient);
Enter fullscreen mode Exit fullscreen mode

The chained extension methods make the code much more descriptive and clear - and it was very easy to achieve with extension methods!

Closing

I hope this has given you some insight into what extension methods are in C# and how we can use them. They allow us to very easily add new functionality to an existing type without having to inherit from or write a wrapper around that type.

As with all design patterns, make sure you don't abuse it! There is a place for everything! While extension methods are neat, they might not always be the solution to your problem. So be careful with how and where you choose to use them.

I hope this article helped. If you have any questions or something to add, please leave a comment below.

Top comments (1)

Collapse
 
davidkroell profile image
David Kröll

Very neat explanation - I liked the diagram which describes the invokation. Maybe it would be worth mentioning how the invokation is really made.
The compiler just rewrites the extension method invokation into a static class method invokation (that's why the class and method need to be static).

You can also call this method yourself, that's why this is also referred to syntax sugar.