.NET, Design

Abolishing Switch-Case Statement and Pattern Matching in C# 7.0

There are many arguments on the web regarding the switch-case statement. It seems that half of the programmers think that switch-case statement is actually an anti-pattern, and other half claims there are in fact use cases for this concept. Usually, the second group tries to prove a point that in some simple situations it is alright to use switch-case, like some really simple checking. My experience tells me other otherwise and I am belonging fully to the first group.

These statements in these so-called “simple situations” often get out of hands, and what we usually end up with large unreadable chunks of code. Not to mention that if the requirements change (and we know that they do), the statement itself has to be modified, thus breaking Open-Close Principle. This is especially the case in enterprise systems and this kind of thinking leads to maintenance hell.

Another problem of switch-case statements (just like if-else statements) is that they combine data and behavior, which is something that reminds us of procedural programming. What does this mean and why is it bad? Well, data in its essence is an information that doesn’t contain behavior.  Treating data as behavior and using it for controlling the workflow of your application is what creates mentioned maintenance hell. For example, take a look at this code:

string data = "Item1";

var action1 = new Action(() => { Console.Write("This is one!"); });
var action2 = new Action(() => { Console.Write("This is two!"); });
var action3 = new Action(() => { Console.Write("This is three!"); });

switch (data)
{
    case "Item1":
        action1();
        break;
    case "Item2":
        action2();
        break;
    default:
        action3();
        break;
}

Console.ReadLine();

Data in this code is variable named data, but also data is the string that is printed on the console. The behavior is the action of printing information on the console, but also the behavior is mapping set of information from the dataset to some action, ie. mapping “Item1” to action1, “Item2” to action2, etc.

switch

As you can see that data is something that can be changed and behavior is something that shouldn’t be changed, and mixing them together is the cause of the problem.

So, how to get rid of switch-case statements?

From Switch-Case to Object-Oriented code

Recently, I was working on the code that relied on the switch-case statement. So, I created next example based on that real-world problem and on the way I refactored it.

public class Entity
{
    public string Type { get; set; }

    public int GetNewValueBasedOnType(int newValue)
    {
        int returnValue;
        switch (Type)
        {
            case "Type0":
                returnValue = newValue;
                break;
            case "Type1":
                returnValue = newValue * 2;
                break;
            case "Type2":
                returnValue = newValue * 3;
                break;
        }

        return newValue;
    }
}

Here we are having entity class with the property Type. This property defines how the function GetNewValueBasedOnType will calculate its result. This class is used in this manner:

var entity = new Entity() { Type = "Type1" };
var value = entity.GetNewValueBasedOnType(6);

In order to modify this example into the proper object-oriented code, we need to change quite a few things. The first thing we can notice is that even though we have multiple Entity types, they are still just Entity types. Donut is a donut, no matter what flavor it is. This means that we can create a class for each entity type, and all those classes should implement one abstract class. Also, we can define an enumeration for the entity type. That looks like this:

public enum EntityType
{
    Type0 = 0,
    Type1 = 1,
    Type2 = 2
}

public abstract class Entity
{
    public abstract int GetNewValue(int newValue);
}

Concrete implementations of Entity class look like this:

public class Type0Entity : Entity
{
    public override int GetNewValue(int newValue)
    {
        return newValue;
    }
}

public class Type1Entity : Entity
{
    public override int GetNewValue(int newValue)
    {
        return 2*newValue;
    }
}

public class Type2Entity : Entity
{
    public override int GetNewValue(int newValue)
    {
        return 3*newValue;
    }
}

That is much better. Now we are closer to real object-oriented implementation, not just some fancy procedural implementation. We used all those nice concepts that object-oriented programming provides us with, like abstraction and inheritance.

Still, there is a question how to use these classes. It seems that we didn’t remove the switch-case statement, we just moved it from Entity class to the place where we will create an object of concrete Entity implementations. Basically, we still need to determine which class we need to instantiate. At this point we can make Entity Factory:

public class EntityFactory
{
    private Dictionary<EntityType, Func<entity>> _entityTypeMapper;

    public EntityFactory()
    {
        _entityTypeMapper = new Dictionary<entitytype, func<entity="">>();
        _entityTypeMapper.Add(EntityType.Type0, () => { return new Type0Entity(); });
        _entityTypeMapper.Add(EntityType.Type1, () => { return new Type1Entity(); });
        _entityTypeMapper.Add(EntityType.Type2, () => { return new Type2Entity(); });
    }

    public Entity GetEntityBasedOnType(EntityType entityType)
    {
        return _entityTypeMapper[entityType]();
    }
}

Now, we can use this code like this:

try
{
    Console.WriteLine("Enter entity type:");
    var entytyType = (EntityType)Enum.Parse(typeof(EntityType), Console.ReadLine(), true);

    Console.WriteLine("Enter new value:");
    var modificationValue = Convert.ToInt32(Console.ReadLine());

    var entityFactory = new EntityFactory();
    var entity = entityFactory.GetEntityBasedOnType(entytyType);
    var result = entity.GetNewValue(modificationValue);

    Console.WriteLine(result);
    Console.ReadLine();
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

Many times I showed this code to someone, I would get a comment that this is too complicated. But, the thing is that this is the way that real object-oriented code should look like. It is more resilient to changes and its behavior is separated from the data. Data no longer controls the workflow. We can see that entity type and entered value are changeable, but that they are not intervened with behavior. By doing that we increased the maintainability of the code.

Another complaint that I usually get is that this code still doesn’t fully satisfy Open Close Principle, meaning that if new entity type needs to be added, we need to change Entity Factory class itself. This is a very good complaint, and it is right on point. Nevertheless, we shouldn’t forget that code that fulfills SOLID principles is something we should always strive to, but many times we can not quite get there. In my opinion, Open Close Principle is the most unreachable of all SOLID principles.

Pattern Matching

You might ask yourself why is this chapter here. We were talking about the switch-case statement, so what does pattern matching has to do with this? Well, remember when I said that I think that switch-case shouldn’t be used? The thing is that C# 7 introduces this feature – pattern matching, which is already well established in functional programming languages, like F#. This feature heavily relies on the switch-case statement, so let’s take a moment and go bit deeper in this and see if this could change my mind.

What is pattern matching after all? To better explain this I’ll use F# example. F# has this mighty concept called discriminated union. Using this concept we can define a variable, that can have multiple types, meaning not only value is changeable but also the type is changeable too. Wait, what? Here is an example:

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float

This means is that we have defined type Shape, that can be either Rectangle or Circle. Variable of type shape could be either of these two types. Like Schrodinger’s cat, we will not know the type of that variable until we take a closer look. Now, let’s say we want to write a function that will get the height of the shape. That would look like something like this:

let getShapeHeight shape =
    match shape with
    | Rectangle(width = h) -> h
    | Circle(radius = r) -> 2. * r

What happened here is that we checked the type of the variable shape, and returned Rectangle width if the shape is of type Rectangle, or returned two times radius value if the shape is of type Circle. We have opened the box and collapsed a wavefunction.

Cat

Although this concept seems unnecessarily complicated at first, in practice it is both elegant and powerful. Controlling the workflow this gives us so many possibilities, so I was very happy when I saw that it will be a part of new C#.

So how this code would look like in C# before improvements?

var shapes = new Dictionary<string, object>
{
    { "FirstShape", new Rectangle(1, 1) },
    { "SecondShape", new Circle(6) }
};

foreach(var shape in shapes)
{
    if (shape.Value is Rectangle)
    {
        var height = ((Rectangle)shape.Value).Height;
        Console.WriteLine(height);
    }
    else if (shape.Value is Circle)
    {
        var height = ((Circle)shape.Value).Radius * 2;
        Console.WriteLine(height);
    }
}

What this feature giving us in C# is the ability to simplify this syntax. It is giving a little bit more usability to the switch statement too, meaning that now we can switch by the type of the variable.

foreach (var shape in shapes)
{
    switch(shape.Value)
    {
        case Rectangle r:
            Console.WriteLine(r.Height);
            break;
        case Circle c:
            Console.WriteLine(2 * c.Radius);
            break;
    }
}

Another thing that can be done now is using of when clause.

foreach (var shape in shapes)
{
    switch(shape.Value)
    {
        case Rectangle r:
            Console.WriteLine(r.Height);
            break;
        case Circle c when c.Radius > 10:
            Console.WriteLine(2 * c.Radius);
            break;
    }
}

Conclusion

To be honest, I am not very impressed with possibilities that pattern matching in C# 7.0 provided us with. What makes this concept so powerful in F# are basically discriminated unions, and without them, pattern matching in C# is pretty limited. Also, I haven’t done any real world project using this C# version, so I can not really be the judge of it. My impression is that, switch-case statements got a little bit charged up, and things can look little bit cleaner using them, but that they are still sort of anti-pattern. In the end, I didn’t get convinced that these statements have a valid use case and that they should be used.

What I haven’t covered in this post is Strategy pattern, which is also one can be a good choice in some situations, if you want to remove switch-case from your code.

Read more posts from the author at Rubik’s Code.

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.

8 thoughts on “Abolishing Switch-Case Statement and Pattern Matching in C# 7.0”

  1. +1 C# needs records and discriminated unions for pattern matching to become useful. That and like 20 other features that F# has already got…

    Like

  2. > Another complaint that I usually get is that this code still doesn’t fully satisfy Open Close Principle

    I think the disconnect is that you cite Open/Closed as the reason for making the change, but the end result still violates it. So it’s hard to see the advantage.

    You could use reflection or Strategy to fully satisfy Open/Closed (see below), but even then I think it comes down to choosing the right tool for the right job….I.e., if the system is small and is going to be recompiled whenever a change is made, YAGNI matters more than Open/Closed, so you might as well keep the switch. For a large and complex system, it may make more sense to go the abstract route.

    As far as Open-Closed in this particular example—what you could do is use a Factory for each entity type with Strategy to select the appropriate one. (Inject the factories into EntityFactoryStrategy by hand or through a DI framework.)

    [AttributeUsage(AttributeTargets.Class)]
    public class EntityTypeAttribute : Attribute
    {
    public EntityType EntityType { get; set; }

    public EntityTypeAttribute(EntityType entityType)
    {
    EntityType = entityType;
    }
    }

    public abstract class EntityFactory
    {
    public abstract Entity Create();
    }

    [EntityType(EntityType.Type0)]
    public class Type0EntityFactory : EntityFactory
    {
    public override Entity Create()
    {
    var entity = new Type0Entity();
    // …
    // Type0Entity configuration goes here
    // …
    return entity;
    }
    }

    // …

    public class EntityFactoryStrategy
    {
    private IEnumerable _factories;

    public EntityFactoryStrategy(IEnumerable factories)
    {
    _factories = factories;
    }

    public Entity Create(EntityType type)
    {
    return _factories
    .SingleOrDefault(x => x.GetType().GetCustomAttribute().EntityType.Equals(type))
    .Create();
    }
    }

    Or if the classes have parameterless constructors like in your original example, you could stick with strings and use reflection:

    public class EntityFactory
    {
    public Entity GetEntityBasedOnType(string type)
    {
    var entityType = Assembly
    .GetAssembly(typeof(Entity))
    .GetTypes()
    .SingleOrDefault(x => x.IsSubclassOf(typeof(Entity)) && x.Name.StartsWith(type, StringComparison.InvariantCultureIgnoreCase));

    return (Entity)Activator.CreateInstance(entityType);
    }
    }

    Liked by 1 person

    1. Hey Eric,

      Thanks for reading. Yes, further in the article, I am mentioning that my change still violates Open/Close principle.

      Regarding the Strategy pattern, it was out of my scope for this particular post, because I wanted to cover pattern matching.

      I really like your solution with abstract factory and strategy, thank you for posting it.

      Cheers,
      Nikola

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s