Extension methods in C# .NET part 1: the basics

Introduction

Extension methods are simply cool. They allow you to extend the functionality of types that you didn’t write and don’t have direct access to. They look like integral parts of any built-in classes in .NET, e.g.:

DateTime.Now.ToMyCustomDate();
string.ToThreeLetterAbbreviation();

You can extend the following types in C#:

  • Classes
  • Structs
  • Interfaces

You can extend public types of 3rd party libraries. You can also extend generic types, such as List of T and IEnumerable of T. You cannot extend sealed classes.

Extension methods are often used to extend the functionality of the code which is out of the developer’s control, like a 3rd party code base. They also come in handy when extending a class without inheritance or composition.

Let’s dive into extension methods then!

The basics: extending system types

Open Visual Studio and insert a new blank solution called ExtensionMethods. Add to this a C# class library called ExtensionMethods.ExtensionSamples. Insert a folder called Basics to the library project. Insert a class called SampleExtensions to this folder. There are a couple of rules around constructing extension methods:

  • The containing class, like SampleExtensions in this case, must be public and static
  • The extension method itself must be static
  • The type you’re extending must be provided as the first argument in the parameter list. It also must be provided with the ‘this’ keyword

So if you want to extend the System.DateTime class with a method that returns a string then you’ll need the following structure:

public static class SampleExtensions
{
	public static string ToCustomFormat(this DateTime dateTime)
	{

	}
}

This will ensure that whenever we use a DateTime object we can call the ToCustomFormat() method on it. Let’s just add some dummy return value to the method:

public static string ToCustomFormat(this DateTime dateTime)
{
	return "This is some custom datetime format.";
}

Let’s see how you can call this method. Add a new Console project called ExtensionCaller to the solution and add a reference to the library project. Add the following code to Main:

string customDateFormat = DateTime.Now.ToCustomFormat();
Console.WriteLine(customDateFormat);
Console.ReadKey();

You’ll notice that the compiler doesn’t like it:

‘System.DateTime’ does not contain a definition for ‘ToCustomFormat’ and no extension method ‘ToCustomFormat’ accepting a first argument of type ‘System.DateTime’ could be found

You need to add a using statement to the namespace where the extension method is located in Program.cs:

using ExtensionMethods.Extensions.Basics;

The code should compile now. Run the code and you’ll see our little string “This is some custom datetime format.” printed on the console windows. Congratulations, you have just added custom functionality to a native .NET type that is clearly out of direct control.

You can also provide “normal” input parameters to extension methods. All parameters after the first ‘this’ will be input parameters. Let’s see how we can extend the built-in String object. Back in SampleExtensions.cs add the following method:

public static string ToEmail(this string fullName, string nickName, int yearOfBirth)
{
	return string.Concat(fullName.ToLower().Replace(" ", "."), yearOfBirth, "@", nickName.ToLower().Replace(" ", string.Empty), ".com");
}

You can use it from Main as follows:

string fullName = "Elvis Presley";
int birthYear = 1935;
string nickName = "King of Rock and Roll";
string email = fullName.ToEmail(nickName, birthYear);
Console.WriteLine(email);

The output should be elvis.presley1935@kingofrockandroll.com. As you start typing “fullName.” IntelliSense will find the extension method and denote it with a little downward pointing arrow. That’s a telling sign of extension methods: you’ll know you’re not using a native, built-in method of the type if you see that arrow. You’ll in addition see “(extension)” in the description provided by IntelliSense. If IntelliSense doesn’t find the method you’re looking for then double check if the correct namespace has been referenced among the using statements. It won’t find the correct namespace for you. In all other aspects extension methods behave just like normal methods do.

As you see extension methods can be located in just any assembly within your solution. They can also reside in any namespace, even namespaces you don’t own, such as System.String which will alleviate the problem mentioned in the previous paragraph, i.e. the need to import the namespace where the extension method is located. Just make sure to reference the correct library and namespace in the consuming classes. Also, the static class encapsulating the extension methods can have any name as it won’t appear in the consuming classes: normally the word “Extensions” is added to the class name for clarity, such as “StringExtensions”, “DateExtensions”, “PriceExtensions”, but it could be MickeyMouse or DonaldDuck as well.

Let’s continue with more examples. Add a new folder called Strings to the ExtensionSamples project. Add a new class called CustomStringExtensions and make it public and static. Change the namespace to simply “System”. Add the following extension method:

public static string ToXmlName(this string name)
{
	return string.Concat("<name>", name, "</name>");
}

In Main start typing the following:

string xmlName = fullName.tox

IntelliSense will find the ToXmlName() method for you because there’s no extra namespace that must be imported. System is already present:

string xmlName = fullName.ToXmlName();

Let’s see how to add an overloaded extension method:

public static string ToXmlName(this string name, string customAttrib)
{
	return string.Concat("<name attrib=", customAttrib, ">", name, "</name>");
}

So in fact it’s not much different from overloading “normal” methods except for the ‘this’ parameter.

In Main:

string xmlName2 = fullName.ToXmlName("singer");

IntelliSense will show both methods but hides the ‘this’ parameter. It will only show the possible input parameters.

Extending interfaces

Add a new folder to the ExtensionSamples project called Interfaces. In this folder add a new class called Product:

public class Product
{
	public int Id { get; set; }
	public string Name { get; set; }
}

Imagine that the products can be retrieved from a variety of repositories which all implement the following interface:

public interface IProductDataSource
{
	IEnumerable<Product> GetProducts();
}

Also, they inherit from separate abstract classes. This shows the scenario that each repository is different enough so that it’s not reasonable to make them inherit from the same abstract base class:

public abstract class FileSourceBase
{
	public string FileName
	{
		get
		{
			return "source.xml";
		}
	}
}

public abstract class DatabaseSourceBase
{
	public long Size
	{
		get
		{
			return 1000;
		}
	}
}

public abstract class ApiSourceBase
{
	public string Username
	{
		get
		{
			return "apiuser";
		}
	}
}

Add the following implementations:

public class ApiSourcepublic : ApiSourceBase, IProductDataSource
{
	public IEnumerable<Product> GetProducts()
	{
		return new List<Product>() { new Product() { Id = 5, Name = "Fifth" }, new Product() { Id = 6, Name = "Sixth" } };
	}
}

public class FileSource : FileSourceBase, IProductDataSource
{
	public IEnumerable<Product> GetProducts()
	{
		return new List<Product>() { new Product() { Id = 3, Name = "Third" }, new Product() { Id = 4, Name = "Fourth" } };
	}
}

public class DatabaseSource : DatabaseSourceBase, IProductDataSource
{
	public IEnumerable<Product> GetProducts()
	{
		return new List<Product>() { new Product() { Id = 1, Name = "First" }, new Product() { Id = 2, Name = "Second" } };
	}
}

A common functionality needed from each data source is to search for specific products regardless of the source, e.g. retrieve all products whose name includes “f”. The goal is now to add this function by an extension method. Add a new class to the same folder called DataSourceExtensions and make it… …public and static, as you know by now. Double-check that the namespace is the same as for the IProductDataSource interface. This is so that the extension will be available anywhere the interface is available without the need to add extra using statements.

Add the following stub extension method which extends the interface:

public static IEnumerable<Product> SearchProduct(this IProductDataSource source, string searchTerm)
{
	return null;
}

It can be called from Main as follows:

IProductDataSource productDataSource = new DatabaseSource();
IEnumerable<Product> products = productDataSource.SearchProduct("f");

We had to import the namespace of the interface but then the SearchProduct extension was readily available. The implementation of the method is simple as each concrete class has a method which returns all products. All we need is some LINQ search:

public static IEnumerable<Product> SearchProduct(this IProductDataSource source, string searchTerm)
{
	return from p in source.GetProducts() where p.Name.ToLower().IndexOf(searchTerm.ToLower()) > -1 select p;
}

As we’re extending an interface, the extension method will be available for all implementing types. There’s no need to cast the concrete type to an interface type. Also, if you get new implementing classes in the future then the extension method will be automatically available for them as well.

Read the next post in this series here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

5 Responses to Extension methods in C# .NET part 1: the basics

  1. Hi Andras!
    It’s possible to extend a sealed type. E.g DataContractSerializer or you own sealed class.
    And strictly speaking DateTime is a struct not a class 😉
    Greetz

  2. George says:

    Hi Andras (btw, your name in Greek means Man)

    I am trying to follow your tutorial, however when I am type ‘using ExtensionMethods.Extensions.Basics;’ I am getting a missing namespace/assembly reference error. I am using VS2010 with all the updates installed.

    Do you have any idea why this is happening?
    George

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.