I’m sure everyone with at least some background in C# is aware of extension methods — a nice feature that lets developers extend existing types with new methods.

This is extremely convenient in cases where you want to add functionality to types you don’t have control over. In fact, I think everyone at some point authored extensions for the base class library just to make some things more accessible.

But besides the more obvious use cases, there are a few other interesting patterns that directly rely on extension methods, showing how they can used in a slightly less conventional way.

Adding methods to enums

An enum is simply a set of constant numeric values with names uniquely assigned to them. Even though all enums in C# inherit from the Enum abstract class, they are not really treated as classes. This limitation, among other things, prevents them from having methods.

There are some cases where having logic on an enum may be helpful. One of those cases is where a value of an enum can have multiple different representations and you want to be able to easily convert to one of them.

For example, imagine the following type in a generic application that can save files in various formats:

public enum FileFormat { PlainText , OfficeWord , Markdown }

This enum defines a list of formats the application supports and is probably used in various places in code to invoke branching logic depending on its value.

Since each file format can also be represented by its file extension, it would be nice if FileFormat contained a method to get it. This is where we can use an extension method to do it like this:

public static class FileFormatExtensions { public static string GetFileExtension ( this FileFormat self ) { if ( self == FileFormat . PlainText ) return "txt" ; if ( self == FileFormat . OfficeWord ) return "docx" ; if ( self == FileFormat . Markdown ) return "md" ; throw new ArgumentOutOfRangeException ( nameof ( self ) ) ; } }

Which in turn allows us to do the following:

var format = FileFormat . Markdown ; var fileExt = format . GetFileExtension ( ) ; var fileName = $"output. { fileExt } " ;

Refactoring model classes

There are cases where you may not want to add a method directly to a class, for example when it’s an anemic model.

Anemic models are typically represented by a set of public get-only immutable properties, so adding methods to a model class may make it look impure or may give off a suspicion that the methods are accessing some private state. Extension methods don’t have that problem because they can’t access model’s private members and, by nature, aren’t part of the model itself.

So consider this example of two models — one represents a closed caption track for a video and another represents an individual caption:

public class ClosedCaption { public string Text { get ; } public TimeSpan Offset { get ; } public TimeSpan Duration { get ; } public ClosedCaption ( string text , TimeSpan offset , TimeSpan duration ) { Text = text ; Offset = offset ; Duration = duration ; } } public class ClosedCaptionTrack { public string Language { get ; } public IReadOnlyList < ClosedCaption > Captions { get ; } public ClosedCaptionTrack ( string language , IReadOnlyList < ClosedCaption > captions ) { Language = language ; Captions = captions ; } }

In the current state, if someone wanted to get a closed caption displayed at specific point in time, they would have to run a LINQ like this one:

var time = TimeSpan . FromSeconds ( 67 ) ; var caption = track . Captions . FirstOrDefault ( cc => cc . Offset <= time && cc . Offset + cc . Duration >= time ) ;

This really calls for a helper method of some sorts which can be implemented either as a member method or an extension method. I personally prefer the latter.

public static class ClosedCaptionTrackExtensions { public static ClosedCaption GetByTime ( this ClosedCaptionTrack self , TimeSpan time ) => self . Captions . FirstOrDefault ( cc => cc . Offset <= time && cc . Offset + cc . Duration >= time ) ; }

An extension method here achieves the same thing as a normal method, but it provides a few subtle benefits:

It’s clear that this method only works with public members of the class and doesn’t mutate its private state in some obscure way. It’s obvious that this method simply provides a shortcut and it’s there only for convenience. The method is part of an entirely separate class (or even assembly) which helps us separate data from logic.

Overall, using an extension method approach here helps draw a line between what’s necessary and what’s helpful.

Making interfaces more versatile

When designing an interface you always want to keep the contract minimal in order to make it easier to implement. It helps a lot when your interface exposes the most generic type of functionality so that others (or you) can build on top of it and cover more specific cases.

If that didn’t make any sense, here’s an example of a typical interface that saves some model to a file:

public interface IExportService { FileInfo SaveToFile ( Model model , string filePath ) ; }

It works just fine, but a few weeks later you come back to it with a new requirement — classes implementing IExportService , on top of exporting to a file, should now also be able to write to memory.

So in order to satisfy that requirement, you add a new method to the contract:

public interface IExportService { FileInfo SaveToFile ( Model model , string filePath ) ; byte [ ] SaveToMemory ( Model model ) ; }

This change just broke all existing implementations of IExportService because now all of them have to be updated to support writing to memory as well.

Instead of doing all that, we could have designed the initial version of the interface slightly differently:

public interface IExportService { void Save ( Model model , Stream output ) ; }

This way, the interface enforces writing to the most generic destination, a Stream . Now we are no longer limited to files and can target a variety of different outputs as well.

The only downside to this approach is that the more basic operations are not as straightforward as they used to be — now you need to set up a concrete instance of a Stream , wrap it in a using statement, and pass it as a parameter.

Fortunately, this downside can be completely negated with the use of extension methods:

public static class ExportServiceExtensions { public static FileInfo SaveToFile ( this IExportService self , Model model , string filePath ) { using ( var output = File . Create ( filePath ) ) { self . Save ( model , output ) ; return new FileInfo ( filePath ) ; } } public static byte [ ] SaveToMemory ( this IExportService self , Model model ) { using ( var output = new MemoryStream ( ) ) { self . Save ( model , output ) ; return output . ToArray ( ) ; } } }

By refactoring the initial interface we’ve made it a lot more versatile and maintainable, and, thanks to extension methods, we didn’t have to sacrifice usability in any way.

All in all, I think extension methods are an invaluable tool that can help us make simple things simple and complex things possible.