Adam Storr - Strategy pattern with enum constraints

Strategy pattern with enum constraints

Strategy pattern implementation options with enum generic constraints

Published on 08 June 2018

In the previous posts (here and here) we've explored a couple of options of how to implement the strategy design pattern. The one thing which kept coming back to me though is that it's not generic. In this post we'll explorer implementing the first iteration using generics with the new C# 7.3 feature.

But does it need to be generic?

In this example it doesn't need to be generic. However the whole point of design patterns is to make code clear, clean and maintainable. If we can have interfaces which make implementations consistent then this can help with readable and maintainable code. This example is just that; an example.

Brief History

Since the publishing of generics in C# in the .NET 2 timeframe, circa 2005, there were restrictions of what types you could apply as a generic constraint. These restrictions stopped, amongst others, using the Enum type as a constraint. As of C# 7.3 this restriction has been removed.

Updating the example

In the example I am working with it wouldn't make sense to remove the Operator enum from the interface definition however this is to show an example of using an Enum as a generic constraint.

Using an Enum constraint the IMathOperator interface can be updated in the following way.

Before:

public interface IMathOperator
{
    Operator Operator { get; }

    int Calculate(int a, int b);
}

After:

public interface IMathOperator<out T> where T : Enum
{
    T Operator { get; }

    int Calculate(int a, int b);
}

Comparing the two we can see there is minimal difference except the generic specification. The generic parameter is defined that the type which can be specified will be covarient as defined by the out keyword more info can be found here.

You will also notice that the generic parameter has a constraint on it which limits what T can be. In this case this has been restricted by the new functionality T : Enum.

Updating the Operators

Once the interface has been updated the implementations need updating as well. Due to the way it has been designed this has minimal changes for the implementations. For example, let's look at the AddOperator.

Before:

public class AddOperator : IMathOperator
{
    public Operator Operator => Operator.Add;

    public int Calculate(int a, int b) => a + b;
}

After:

public class AddOperator : IMathOperator<Operator>
{
    public Operator Operator => Operator.Add;

    public int Calculate(int a, int b) => a + b;
}

DI Registration

Due to the change in the interface definition the registrations will also have to be updated when added to the IServiceCollection in the ConfigureServices method.

services.AddScoped<IMathStrategy, MathStrategy>();
services.AddScoped<IMathOperator<Operator>, AddOperator>();

Conclusion

In this post we have looked at how we can update the IMathOperator interface to add in a generic parameter and now using C# 7.3 specify that the generic parameter must be an Enum.

A full working example can be found on Github.

Any questions/comments then please contact me on Twitter @WestDiscGolf