C# and F# approaches to illegal states

By Vladimir Khorikov

You have probably heard of such phrase as “make illegal states unrepresentable” already. Basically, it stands for using some set of techniques for dealing with illegal states in your domain model.

In this post, we’ll look at how C# and F# allow us to handle them.

C#’s approach

Let’s take a User class as an example:

public enum UserStatus

{

    Active,

    Deleted

}

 

public class User

{

    public string Name { get; private set; }

    public UserStatus Status { get; private set; }

    public DateTime? DeletionDate { get; private set; }

    public List<Subscription> Subscriptions { get; private set; }

}

Here, a user can be either active or deleted. There are two invariants in this example:

  • While in the deleted state, the user must have the deletion date filled out. The subscription collection should be empty.
  • A user can have subscriptions only if he/she is in the active state. The deletion date must not be set in this case.

The technique C# traditionally uses to deal with illegal states is called design by contract. If you use the Code Contracts extension, you may specify the invariants explicitly:

[ContractInvariantMethod]

private void Invariants()

{

    Contract.Invariant(

        (Status == UserStatus.Active && DeletionDate == null) ||

        (Status == UserStatus.Deleted && DeletionDate != null && !Subscriptions.Any()));

}

Alternatively, you can use hand-written precondition checks in public methods of the class. Here is how it can be done in C#:

public enum UserStatus

{

    Active,

    Deleted

}

 

public class User

{

    public string Name { get; private set; }

    public UserStatus Status { get; private set; }

    public DateTime? DeletionDate { get; private set; }

    public List<Subscription> Subscriptions { get; private set; }

 

    public User(string name)

    {

        Name = name;

        Status = UserStatus.Active;

        Subscriptions = new List<Subscription>();

    }

 

    public void Delete()

    {

        Contracts.Require(Status == UserStatus.Active);

 

        Subscriptions.Clear();

        Status = UserStatus.Deleted;

        DeletionDate = DateTime.UtcNow;

    }

 

    public void Undelete()

    {

        Contracts.Require(Status == UserStatus.Deleted);

 

        Status = UserStatus.Active;

        DeletionDate = null;

    }

 

    public void AddSubscription(Subscription subscription)

    {

        Contracts.Require(Status == UserStatus.Active);

 

        Subscriptions.Add(subscription);

    }

}

As you can see, the User class combines all possible operations and then indicates which of them is applicable to what state. We also manually write code required to satisfy the invariants. For example, when we delete a user, we clear the subscription list, as well as set the deletion date.

We can depict this solution in the following way:

C# approach: Cartesian Product of all members and states

C# approach: Cartesian Product of all members and states

The User class is a Cartesian Product of all members and states. The problem here is that some of the combinations are invalid. That is why we use the design by contract approach: it helps us specify in what state the user must reside prior to calling each method.

F#’s approach

F# (as well as other strongly-typed functional languages) traditionally employ a different approach. The bottom line for it is the use of the type system to represent each state separately.

This is how the code above can be rewritten in F#:

type ActiveUser =

    {

    Name: string;

    Subscriptions: Subscription list

    }

 

type DeletedUser =

    {

    Name: string;

    DeletionDate: DateTime;

    }

 

type User =

    | Active of ActiveUser

    | Deleted of DeletedUser

 

let delete activeUser =

    let { Name = name; Subscriptions = _ } = activeUser

    let deletedUser = { Name = name; DeletionDate = DateTime.UtcNow }

    deletedUser

 

let undelete deletedUser =

    let { Name = name; DeletionDate = _ } = deletedUser

    let activeUser = { Name = name; Subscriptions = []}

    activeUser

 

let addSubscription activeUser subscription =

    let { Name = name; Subscriptions = subscriptions } = activeUser

    let activeUser = { Name = name; Subscriptions = subscription :: subscriptions}

    activeUser

Here, we have two separate types – ActiveUser and DeletedUser – which contain only the data that belongs to these user states. Also, the functions now accept either ActiveUser or DeletedUser, but not the User type. For example, the activeUser parameter in the delete function should be of type ActiveUser. If we try to pass it a value of the DeletedUser type, we will get a compilation error.

That gives us a better granularity, so instead of having an all-in-one type, we split it in two:

F# approach:

F# approach: “normalized” schema

Note that unlike the table for the C# version, this one’s cells are all filled out meaning that we now don’t have any invalid combinations of the states, the data, and the functions.

The differences

The latter solution obviously has a big advantage over the former one. It lifts the invariants into the type system, drastically shortening the feedback loop. With F#, there is no need to run the application in order to validate its correctness. The compiler does all the work for us. The run-time checks we employ in C# using code contracts are simply not required in F#.

We can portray the C#’s approach like this:

C# approach to illegal states

C# approach to illegal states

The figure on the left represents the actual shape of the problem we are about to solve. The common way to settle it in C# is to use a coarse-grained solution which, albeit fully covering the problem, brings some redundancy with it. We use the design by contract approach in order to “evict” those redundancies in the run-time.

With F#, on the other hand, we can choose a solution whose shape matches the problem exactly:

F# approach to illegal states

F# approach to illegal states

No need to evict the redundancies, because there isn’t any; we exclude the invalid states from our domain model in the first place. That is the reason why we don’t need to use the design by contract approach in F#.

Conclusion

Unfortunately, the lack of discriminated unions and pattern matching makes the F#’s approach mostly unbearable to implement in C#. It’s possible in some simple cases but still remains pretty tedious.

With F#, we are able to make the full use of the power of algebraic types which are extremely helpful for domain modelling.

Further reading/watching





  • Vasily Kirichenko

    “The technique C# traditionally uses to deal with illegal states is called design by contract.”

    I disagree. You can achieve the same safety properties in C# with a class hierarchy (into which, BTW, F# DUs compiled anyway) plus virtual methods / visitors.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Sure! In practice, however, an all-in-one type approach is used more often. And I think there are good reasons for that. The lack of pattern matching on one hand and the necessity to write more code on the other makes the fine-grained approach less attractive with C#.

      In this simple example there’s no need in pattern matching of course, but for a more or less complex solution the differences become apparent.

      • Michael Yanni

        I understand what you are saying, but I think the rationale of why it is done in C# is incorrect. In general ‘plain Jane’ C#, you shouldn’t need the use of an enum. Enums exist to map to external systems (usually). In the sample above, it may be mapped to a database schema. I’ve used them extensively to map to command lists of embedded systems.

        However, writing C# in a standard OOP design, you would create an ActiveUser and a DeletedUser that derive from User (User only contains Name). Also, I still have yet to work for a professional .NET house that uses Contracts, simply because Microsoft seems to have given up on their own implementation of them (they are ‘somewhat’ supported in VS, and buggy at best).

        Overall, I don’t think it is a language issue that causes these different patterns. So, please don’t blame C#. With C# 6.0, making simple examples like this except using inheritance becomes even less code than it was prior to 6.0. Additionally, the inheritance is clear if you took your Active / Deleted User tables pictured above and did a Cartesian Product of those. You’d see that Name is ‘Yes’ on both tables in both columns.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          This is a pretty simple example, so replacing the User class with two separate classes in C# would indeed be easy. Nevertheless, imagine how much more code it would require if there are:

          1) 4-6 different states a user might be in
          2) Additional property, for example, Level (with Gold, Silver, Regular, etc. values)

          Handling this requirement with inheritance leads to a verbose code which is hard to maintain and understand. Enums is what is usually used in such situations in C#, as it provides a nice trade-off between verbosity and accuracy. There still are cases where inheritance fits better, of course, but there should be a more significant reason to use them than just user states.

          BTW, while Microsoft don’t support the Code Contracts tool anymore, there is a chance they would be implemented on the language level at some point in future: https://github.com/dotnet/roslyn/issues/119. So I wouldn’t say MS gave up on them, we might see the second birth of these ideas soon.

          • Michael Yanni

            I still don’t believe that Enums should be (or are) a normal occurrence in C# programming. Using Enums to hold state and pass that state to other methods is a very procedural mindset. In general, a skilled C# programmer should be able to use design patterns, object composition, and interfaces to approach complex situations that involve states between actions in a system.

            For example, the ‘Status’ you described of Gold, Silver, Regular (let’s just use these three) don’t mean anything by themselves. Enums, realistically, should only be used to map a name to a value. However, it sounds like you are using them to run different logic in a method when the logic should actually be using objects to determine what it should do.

            Overall, functionality and behavior shouldn’t be directly mapped to a Enum. They are strictly a name mapped to a value. An example of something that one would think would be an enum is the Color struct of .Net (https://msdn.microsoft.com/en-us/library/system.drawing.color(v=vs.110).aspx).

            You would think that an enum where each color by name (Green, Blue, Red, etc) are applied to the ARGB value (#FF0000FF), but they use a Struct because of the number of operations that can be performed on a Color (separate color values (RGB), hue value, saturation value, etc). You’ll see that KnownColor enum exists, but that is only for legacy purposes. Again, showing that they can be used for interaction between other systems (in this case, a legacy system for storing Color value).

          • http://enterprisecraftsmanship.com/ Vladimir Khorikov

            It’s just a matter of granularity. It’s a good practice to use composition over inheritance in C# (and other OO languages). Enums provide this sort of composability in simple cases. For more complex scenarios, it’s okay to use structs/classes and include them into the class as components.

            The main point I was trying to convey is that inheritance, while simulating the F#’s approach, is not the best way to solve these kinds of problems in C#.

          • David Raab

            Using Enums to hold state and pass that state to other methods is a very procedural mindset. In general, a skilled C# programmer should be able to use design patterns, object composition, and interfaces to approach complex situations that involve states between actions in a system.

            1) It is not wrong to have something procedural.
            2) It is also not a complex situation. 4-5 different states is pretty easy.
            3) When you are forced to use Design Pattern like the Visitor Pattern that forces you to write like 10 classes and 10 times more code for that simples example, then it is definitely a problem that the language is not powerful enough.
            4) Being 100% pure OO is not a goal. Defining an Enum is like 3 lines of code and that it. The Visitor Pattern should be an Anti-Pattern, the amount you have to write for that Pattern is just insane. If an enum solves that problem in 3 lines of code it is the best way to use that. Achieving 100% pure OO should not be the goal. The goal should be having readable easy to read and understandable code. An enum fulfill this. The Visitor Pattern doesn’t do that. That why i stated 1)

        • Harry McIntyre

          I agree with Micheal, this isn’t a language issue, it’s how you use it.

          It’s worth reading (as I’m sure you have), Eric Lippert’s series wizards and warriors also, on the fun you can have mapping business rules into a type system.

          http://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/

  • http://blog.abodit.com/ Ian Mercer

    You could also have made `UserState` a class with subclasses: `UserStateActive` has `Subscriptions` and `UserStateDeleted` has a `DeletionDate`. Subclassing User (i.e. using inheritance instead of using composition) is rarely a good idea IMHO because a user has many aspects (admin, deleted, online, delinquent, …). Which do you pick and why? Also, a User doesn’t really have a deletion date, it’s really their membership of the site that has a deletion date, and, if they rejoin they may have another later. Treating ‘state’ separately from the ‘thing’ allows the storage model to evolve to, for example, audit each change in state. The `UserState` property on a User becomes the most recent state for a user in an event-source containing all their past states.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      The approach with UserState is interesting, but I think the lack of mattern matching in C# diminishes its benefits. If we need to write a method that processes a user differently depending on their state, the code would be quite messy:

      public ProcessUser(User user)
      {
      if (user.State is UserStateActive)
      { }
      else if (user.State is UserStateDeleted)
      { }
      }

      The approach you propose does reduce the problem with inheritance but doesn’t make it go away completely, unfortunately.

      • David Raab

        An additional problem that raises is if you add another state. Your ProcessUser Method now will not handle that state. It would be at least good to an “else” branch that throws an exception for unknown states.

        But you easily can forget those things. And without searching your code it get hards to find all cases where you do something based on state.

        That’s also the nice thing with matching in F#. You already get a list at compile-time of where you forget to handle the new case.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          Indeed! An additional “else throw” statement helps here but it’s still only a run-time check. A better way would be to raise this validation to the compilation stage, just as F# does.

      • Michael Yanni

        The closest you can get to pattern matching in C#. I did this in 6.0 to remove the need for Tuple.Create for each state.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          Interesting approach!

          I like static type checking benefits in this code. At the same time, I hope we will get native support for pattern matching in C# 7. Manual workarounds like this one and the one David described below look quite verbose to me.

  • Graeme Wicksted

    Composition and functional style are two hallmarks of great code. The latter applies more to iterations (read: filtering) and transformation. The former covers this case exactly. The C# approach used here may be perfectly valid as a DTO but should not communicate validity in any way (since the data store likely does not at this level). It may still be logged or handled post-deserialization. Nevertheless type constraints are an expressive way of handling validation. It was a nice reminder and I’m sure it has several of us thinking differently. Thank you for the great write-up!

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Good points, thanks!

  • David Raab

    There is a way to get something similar to work in C#. But it is a lot more closer to a functional solution instead an OO solution. And i think most OO programer with less experience of a functional solution will not understand it. But it is at least a lot shorter than the horrible Visitor Pattern.

    The solution works that you just have an “IUser” interface. This interface only supports a “Match” method. This method basically do what the “match” statement in F# do. It expects a lambda for each case. You now can have different classes for each state. And each class can have its own methods or attributes without that you need an interface or do some ugly inheritance stuff. And you also get the same benefit that if you add a new state you have to update your code everywhere.

    But it is still a pretty long solution especially compared to the short F# code.


    public interface IUser {
    TResult Match<TResult>(
    Func<ActiveUser, TResult> active,
    Func<DeletedUser, TResult> deleted
    );

    void Match(
    Action<ActiveUser> active,
    Action<DeletedUser> deleted
    );
    }

    public class ActiveUser : IUser {
    public string Name { get; private set; }
    public string Foo { get; private set; }
    public ActiveUser(string name, string foo) {
    this.Name = name;
    this.Foo = foo;
    }

    public TResult Match<TResult>(Func<ActiveUser,TResult> active, Func<DeletedUser,TResult> deleted) {
    return active(this);
    }

    public void Match(Action<ActiveUser> active, Action<DeletedUser> deleted) {
    active(this);
    }
    }

    public class DeletedUser : IUser {
    public string Name { get; private set; }
    public DateTime DeletionDate { get; private set; }
    public DeletedUser(string name, DateTime deletionDate) {
    this.Name = name;
    this.DeletionDate = deletionDate;
    }

    public TResult Match<TResult>(Func<ActiveUser,TResult> active, Func<DeletedUser,TResult> deleted) {
    return deleted(this);
    }

    public void Match(Action<ActiveUser> active, Action<DeletedUser> deleted) {
    deleted(this);
    }
    }

    public static class User {
    public static IUser Create(string name, string foo) {
    return new ActiveUser(name, foo);
    }

    public static IUser DeleteUser(IUser user) {
    return user.Match(
    active => new DeletedUser(active.Name, DateTime.UtcNow),
    deleted => deleted
    );
    }

    public static void PrintUser(IUser user) {
    user.Match(
    active => Console.WriteLine("Active User: {0} with special foo {1}", active.Name, active.Foo),
    deleted => Console.WriteLine("Deleted User {0} at time {1}", deleted.Name, deleted.DeletionDate)
    );
    }
    }

    class Program {
    static void Main(string[] args) {
    var user = User.Create("Me", "Bar");
    User.PrintUser(user);
    var deletedUser = User.DeleteUser(user);
    User.PrintUser(deletedUser);
    }
    }

  • Nibras Manna

    Apples to oranges comparison. Should’ve used class hierarchy in the C# example along with F#’s type system. Not an enum.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Hi Nibras, thanks for your comment.

      Inheritance doesn’t solve the underlying problem here. Instead, it amplifies it even more. “Composition over inheritance” OO best practice is just about.