DEV Community

Aviad Pineles
Aviad Pineles

Posted on

C# - Enums and Bug Resilience

Our domain is made of sets. Some sets are trivial such as all integers (int) or all booleans (bool) but often we are dealing with sets that are more narrow, for example the possible states of an order (Incomplete, Unpaid, Paid, Shipped, etc.).

In our database we assign a number or a string that represents each possible value, and in our code we use an Enum for that. This is good, but if we are not careful, then at some point in the future, when we change the composition of the set (for example by adding another state Received), we might create hard to debug errors in our program.

We want to always use switch when dealing with these sets, so that when the set changes, the compiler automatically warns us about all the places where code might need to be changed.

However there's one case where this is tricky: when dealing with the database. For example, we have a query in our code that fetches all orders that are paid, so our LINQ condition could be something like this:

where order.status == OrderStatus.Paid ||
      order.status == OrderStatus.Shipped
Enter fullscreen mode Exit fullscreen mode

But what happens when we now add the new Received state? This query is now broken because received orders are also paid, but the compiler doesn't have any way of knowing that!

We need to somehow add a switch to this operation to make it safe. What we must do is filter all the elements of the enum by our condition:

IEnumerable<OrderStatus> GetOrderStatusValuesWhichArePaid() {
    static bool IsPaid(OrderStatus orderStatus) => orderStatus switch {
        OrderkStatus.Incomplete => false,
        OrderkStatus.Unpaid => false,
        OrderkStatus.Paid => true,
        OrderkStatus.Shipped => true,
        _ => throw new ArgumentOutOfRangeException(nameof(orderStatus))
    };

    return Enum.GetValues<OrderStatus>().Where(IsPaid);
}
Enter fullscreen mode Exit fullscreen mode

And we can use it in our LINQ query:

var paidStatusValues = GetOrderStatusValuesWhichArePaid();
var query = from order in Orders
            where paidStatusValues.Contains(order.status)
            select order;
Enter fullscreen mode Exit fullscreen mode

Now when we add the new Received state, the compiler can warn us that the switch statement in the function IsPaid is not covering all cases properly.

Discussion (0)