C# Extend with Extension Method
Many C# developers know the existence of Extension Methods. It gives developers the ability to extend functionalities of the existing API without modifying the original source code.
A simple example is to check if a string is a numeric value. There are more than one solution to solve that problem. And one of them is that we want to write code like this:
string houseNumber = "123";
bool isNumber = houseNumber.IsNumeric();
The IsNumeric
is not a built-in method on the string
type. With the extension method, we could implement the IsNumeric
easily.
public static bool IsNumeric(this string input)
{
return Regex.IsMatch(input, "^[0-9]+$");
}
That is the basic concept. LINQ is the main consumer of the benefit. Or we might say that LINQ was the reason why the extension method concept was born. Of course, we do not have to know it to use LINQ.
So what is this post about? We could take advantages of the extension method to improve our design and implementation even when the code is under development, which means we are free to change our code.
Take a look at this simple interface
public interface IDocumentRepository
{
/// <summary>
/// Get a document of given type <T> by id. Return null if not found
/// </summary>
T GetDocument<T>(Guid id);
}
A simple, generic repository. The detail implementation does not matter here.
Later in the development, there are many places having this kind of logic (code)
var document = _repository.GetDocument<Product>(id);
if(document == null)
throw new ObjectNotFoundException();
// Do something with the document here
Just some checking. It is not a big deal, except the fact that we need to do it in many places. So we want the ability to write this code
var document = _repository.GetOrThrow<Product>(id);
So what are options?
Modify the interface and implementation
One could go with this approach
public interface IDocumentRepository
{
/// <summary>
/// Get a document of given type <T> by id. Return null if not found
/// </summary>
T GetDocument<T>(Guid id);
T GetOrThrow<T>(Guid id);
}
And implement the method in all derived classes. The approach works but I do not like it much for a couple of reasons
- That logic does not fit there naturally. It is a logic added on the existing functionality
- All the implemented classes are modified. In the best case, there is only one implemented class
- And what about later we need other kind of that functionalities? Will we keep expanding the interface?
- Usually the interface is at the infrastructure, but the extended functions are in the service or small bounded contexts. Some functions do not make any sense in other boundaries
Extend by extension methods
We could keep the interface clean as it was designed and extend the function by using extension methods as shown
public static class DocumentRepositoryExtensions
{
public static T GetOrThrow<T>(this IDocumentRepository repository, Guid id)
{
var document = repository.GetDocument<Product>(id);
if(document == null)
throw new ObjectNotFoundException();
return document;
}
}
And we are free to place this code in the place that uses it. If it is required in many layers, assemblies, consider to put it in a separate assembly.
However, be reasonable otherwise you end up with extension methods everywhere. It is about placing the responsibilities in the right place.
I have been using this approach in my recent projects. And I am glad I have done it!