Extension Methods

Learn how to add new methods to existing types without modifying their source code using extension methods.

We often need to extend the functionality of a class or struct. We can use several approaches depending on the type and our access to the source code.

Update source code

We could go to the source code of the type and make whatever changes we need. Consider the following Printer class. Its purpose is to print the Message property in a variety of ways. If we need additional functionality, we can simply add another method or edit existing ones.

C# 14.0
namespace MainProgram;
public class Printer
{
public string Message { get; set; }
public Printer(string message)
{
Message = message;
}
// We can change this method or add another one
public void PrintWithDashes()
{
Console.WriteLine($"###{Message}###");
}
}
  • Line 1: We declare a file-scoped namespace to organize our code without extra indentation.

  • Line 3: We define the Printer class.

  • Lines 5–10: We define a property and a constructor to initialize the printer with a message.

  • Lines 13–16: We add a new method PrintWithDashes directly to the class.

This approach is only useful if we control the source code of the type we want to modify. This isn’t possible in most cases because the code is likely managed by a different person, team, or company.

Now, let’s look at how we would use this modified class in our application.

C# 14.0
namespace MainProgram;
public class Printer
{
public string Message { get; set; }
public Printer(string message)
{
Message = message;
}
// We can change this method or add another one
public void PrintWithDashes()
{
Console.WriteLine($"###{Message}###");
}
}
  • Line 2: We import the MainProgram namespace so we can use the Printer class.

  • Line 5: We create a new instance of Printer using target-typed new.

  • Line 7: We call the method we added to the source code.

Extend with inheritance

Let’s assume we cannot change the Printer class source code, so it remains in its original state. Another approach we could take is to inherit from the type we want to extend. For our Printer class, we can create a new class that inherits from it to add the necessary methods and properties.

C# 14.0
namespace MainProgram;
// Because we cannot change the source code of the Printer class,
// we create a different class that derives from Printer
public class StarPrinter : Printer
{
public StarPrinter(string message) : base(message)
{
}
// Here we can add additional functionality
public void PrintWithStars()
{
Console.WriteLine($"***{Message}***");
}
}
  • Line 6: We define StarPrinter and inherit from the Printer class.

  • Line 8: We chain the constructor to the base class constructor using base(message).

  • Line 14: We add the new PrintWithStars method to this derived class.

Now, we can use our derived StarPrinter to access both the old and new functionality.

C# 14.0
using MainProgram;
string message = "Hello World";
StarPrinter printer = new(message);
// We still have the old method
printer.PrintWithDashes();
// But also, we have this new one
printer.PrintWithStars();
  • Line 4: We instantiate StarPrinter instead of Printer.

  • Line 7: We call the original method inherited from Printer.

  • Line 10: We call the new method defined in StarPrinter.

This approach fails if the type is sealed or if we are working with value types (structs), as they do not support inheritance.

Extension methods

Extension methods provide a solution when we cannot change the source code or inherit from the target type.

Consider the following example. We want to add a method to the System.String class that allows us to reverse a string. We don’t have access to the source code of the System.String class, so we can’t add a new method there. We can create an extension method, however.

C# 14.0
namespace Extensions;
// Static class
public static class StringExtensionMethods
{
// Static method
// Look at the "this" keyword
public static string Reverse(this string stringObject)
{
char[] charArray = stringObject.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
  • Line 4: We define a static class. Extension methods must reside inside a static class.

  • Line 8: We define a static method with the this modifier on the first parameter. The type of the parameter following this specifies the type the method extends (in this case, string).

  • Lines 10–12: We convert the string to an array, reverse it, and return the new string.

Extension methods are static methods that have the this keyword before the first parameter. The type of the parameter following this specifies the type the method extends.

Note: Extension methods can only be hosted inside static classes, and to be able to use extension methods, we must import the namespace in which the static class resides.

A note on method precedence

Now we can use this method on any string object as if it were a native member of the class.

C# 14.0
using Extensions;
string name = "John Doe";
string company = "Educative";
// Execute the method directly on the string object
// as if the method is defined inside the string class
Console.WriteLine(name.Reverse());
Console.WriteLine(company.Reverse());
  • Line 2: We import the Extensions namespace to make the extension methods available.

  • Lines 9–10: We call .Reverse() directly on the string variables name and company.

Extending sealed types

Extension methods are also useful when the type we want to extend is marked with the sealed keyword. We can’t inherit from a sealed type, but we can extend it with extension methods.

First, let’s look at the Person class, which is sealed.

C# 14.0
namespace MainProgram;
// Sealed classes cannot be inherited
public sealed class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
public decimal AnnualSalary { get; set; }
}
  • Line 4: We mark the class as sealed, preventing inheritance.

  • Lines 6–10: We define standard properties for the person.

Next, we define an extension method to calculate the monthly salary.

C# 14.0
using MainProgram;
namespace Extensions;
public static class PersonExtensionMethods
{
public static decimal GetMonthlySalary(this Person person)
{
return person.AnnualSalary / 12;
}
}
  • Line 2: We import MainProgram so we can reference the Person class.

  • Line 8: We define the extension method GetMonthlySalary for the Person type using this Person.

  • Line 10: We perform the calculation using the public AnnualSalary property.

Finally, let’s see how this works in the main program.

C# 14.0
using Extensions;
using MainProgram;
Person person = new()
{
FirstName = "John",
LastName = "Doe",
Age = 34,
Occupation = "Software Developer",
AnnualSalary = 120000m
};
Console.WriteLine($"Hello, my name is {person.FirstName}.");
Console.WriteLine($"I work as a {person.Occupation}.");
Console.WriteLine($"I earn {person.GetMonthlySalary()} dollars per month.");
  • Lines 1–2: We import the necessary namespaces.

  • Line 4: We instantiate a Person object using target-typed new.

  • Line 15: We call GetMonthlySalary() on the person instance, even though the method is not defined inside Person.cs.