Extension Methods in the .NET Framework

Extension methods were first introduced in version 3.5 of the .NET Framework. They allow for easily extending a type without having to recompile or modify the original type. This is useful for extending types of which you don’t own the source code. By implementing an extension method in your code, you can call the method as if it is a native method for the type.

There are differing opinions on when to use extension methods as opposed to other coding styles. Microsoft’s recommended general guidelines state: “We recommend that you implement extension methods sparingly and only when you have to. Whenever possible, client code that must extend an existing type should do so by creating a new type derived from the existing type… For a class library that you implemented, you shouldn’t use extension methods to avoid incrementing the version number of an assembly. If you want to add significant functionality to a library for which you own the source code, you should follow the standard .NET Framework guidelines for assembly versioning.”

Inheritance, as recommended by Microsoft, is always a preferred method for extending and building upon base type functionality. Let’s say you have a base type named Animal. Animal has two methods: Walk() and Sleep(). Now, you have a dog, which is an animal, and you want that dog to bark. You don’t want that method to be on Animal, as not all animals bark. Instead, you will inherit the Animal type and create a new method called Bark(). What if you want your animal to eat? All animals eat, so you don’t want to inherit from Animal, instead, you want to modify the base type to add the extension method of Eat(). By doing this, your derived type of Dog, and any other derived type can use the newly created Eat extension method. Note that some classes are marked as sealed (NotInheritable in VB), meaning you cannot inherit to generate a new class, so extension methods can be used to extend the existing sealed functionality.

Extension methods cannot be used to override an existing method that has the exact same signature. For example, every type has a ToString() method. If you create an extension method of ToString(), the extension method will not be called, but the base type’s method will be called instead. On the other hand, if you create an extension method called ToString(string), your extension method will be called through the overloaded method, provided there isn’t another overloaded method with the same signature.

Another thing to be aware of when implementing extension methods on existing types is the possibility that other extensions can be written against the same types and have the same signature. This will result in a compile error stating that the call is ambiguous. This issue was encountered by the developers of NDepend and written about in their blog post “A problem with extension methods.” Similarly, if the base type is later updated to include a method of the same signature, your extension method will no longer be called. Using the Animal example earlier, if the base type was updated to include the missing Eat() method, then the custom extension method of Eat() will no longer be called, and errors may occur.

All types in the .NET Framework inherit from System.Object. For this reason, it is strongly advised not to add extension methods to System.Object. Adding an extension to System.Object could have inconsistent behavior and increase the possibility of errors when an existing type method is called instead of the extension method. For the same reason, it is strongly advised not to overload an existing method with a different signature, such as the ToString(string) example described previously.

Extension methods are implemented in static classes in C# or in Modules in VB. Inside the class in C#, you will create a static method that contains a first parameter of the type being extended, prefixed with the this modifier. Inside the Module in VB, you will add the method definition attribute of <extension()> to the Sub as well as include the type as the first parameter. The Extension() attribute requires that the VB file Imports System.Runtime.CompilerServices. One additional difference between C# and VB is that VB allows you to pass your type ByRef or ByVal, where C# passes as a val only. Despite this, it is not recommended to pass types ByRef, to reduce code complexity and the chance for errors.</extension()>

The following example, from Microsoft Docs C# Programming Guide, implements an extension method that will count the number of words contained in a String.

using System.Linq;
using System.Text;
using System;

namespace CustomExtensions
{
    // Extension methods must be defined in a static class.
    public static class StringExtension
    {
        // This is the extension method.
        // The first parameter takes the "this" modifier
        // and specifies the type for which the method is defined.
        public static int WordCount(this String str)
        {
            return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}

namespace Extension_Methods_Simple
{
    // Import the extension method namespace.
    using CustomExtensions;
    class Program
    {
        static void Main(string[] args)
        {
            string s = "The quick brown fox jumped over the lazy dog.";
            // Call the method as if it were an 
            // instance method on the type. Note that the first
            // parameter is not specified by the calling code.
            int i = s.WordCount();
            System.Console.WriteLine("Word count of s is {0}", i);
        }
    }
}

The example below, from Microsoft Docs VB Programming Guide, implements an extension that will print the output to a console line and append the appropriate punctuation.

' Declarations will typically be in a separate module.  
Imports System.Runtime.CompilerServices  
  
Module StringExtensions  
    <Extension()>   
    Public Sub PrintAndPunctuate(ByVal aString As String,   
                                 ByVal punc As String)  
        Console.WriteLine(aString & punc)  
    End Sub  
  
End Module  

' Import the module that holds the extension method you want to use,   
' and call it.  
  
Imports ConsoleApplication2.StringExtensions  
  
Module Module1  
  
    Sub Main()  
        Dim example = "Hello"  
        example.PrintAndPunctuate("?")  
        example.PrintAndPunctuate("!!!!")  
    End Sub  
  
End Module

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: