C# Business Logic – Should Extension Methods Be Used?

business-logiccdomain-driven-design

In C# we have Extension methods.

Extension methods enable you to add methods to existing types without
creating a new derived type, recompiling, or otherwise modifying the
original type.

An extension method is a special kind of static method, but they are
called as if they were instance methods on the extended type.

However in one of our lesser design moments, we started replacing concrete classes which contain business logic and moving them to Extension Methods.

The advantages seemed neat at first e.g we could replace something like this (very simplified example).

var AddressUtils = new AddressUtils();
AddressUtils.CleanAddress(order);

var ValidationUtils = new ValidationUtils();
ValidationUtils.ValidateCustomer(Order);

with

Order.CleanAddress();
Order.ValidateCustomer();

I.e we were replacing a lot of Stateless instantiable Classes with short-hand static methods (syntactic sugar) that made the code more readable. This has really never sat well with me.

We now have a bunch of static classes with miscellaneous methods in them, the vast majority aren't even generic or interface driven. I.e single use.

Adding to this there is this common notional that static classes are not a good fit for test driven development.

So the alternatives really are

  • Go back to concrete util style helper classes
  • And/Or Encapsulate the logic in some sort of DI service
  • Or add the logic to the Model it self.
  • Or having reusable logic sprinkled out over the various services that need to use them.

In regards to the second last option, order (as most of the the models) are actually Data entities, (and that once again) doesn't seem right (or even feasible) to push the business logic down to the domain models

So i'm left with (unless there are some better patterns) helper classes, Extension methods, DI services, some sort of entity based logic, or a unpredictable spaghetti factory.

Best Answer

You cited

Extension methods enable you to add methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.

and that is when you should use them almost exclusively - where you have a case where it would strongly make sense to extend a type by that method, but you cannot do this, neither by creating a new derived type, nor by recompiling, nor by any other means modifying the original type.

The typical scenario for this is: the type is sealed, and it is either not under your control - maybe a 3rd party library - or you want to avoid adding a method which requires an additional dependency which must not be added to the assembly where the type is defined.

So the first thing to check here is if these constraints apply, and if not, the favored place for putting an additional method should be the class itself. If the the code cannot be changed, but the class is not sealed, one might consider to use subclassing instead of an extension method. In this situation, I would prefer extension methods over subclassing unless access to protected members is needed. There is actually no benefit in the use of subclassing in this situation, neither in testability nor code organisation.

About testability: extension methods are in no way easier or harder to unit test than any other static or non-static method. Testability or TDD becomes only a concern when other code which relies on such a method needs to be tested in isolation. So if you run into a situation where you need to "mock out" the extension method itself, it is time to refactor the method into an utility class which can be replaced more easily by a mock for testing purposes.

So usage of extension methods is fine, if you

  • have a method which fits clearly (!) to the area of responsibility of the type you want to extend

  • cannot modify the type itself easily

  • cannot or do not want to use inheritance

  • don't have the requirement for mocking the function in code which relies on it.

Note there is actually no reason why you cannot start with an extension method, and when you notice you run into its restrictions, refactor the code. Thus, don't overthink this. Note also this has not much to do with the code being business logic or UI code or infrastructure code - when a program is large enough, each of these "layers" can consist of different sublayers and components, which can cause a situation where a "cross-border" type extension is required.