Writing immutable code in C#

Do you write business application, where change requests are on daily basis? Do you write software, which musts be reliable and robust? Do you write components, which could be easily extensible or/and scalable? You can find some interesting thoughts in that article. And following described approach could save your time when maintaining the code.
During last few years there was written quite a lot about immutability, and functional programming as well. Although C# is object oriented programming language, writing the code in immutable approach is not so difficult. Parallel programming is become popular of today. Speeding up processors is no more efficient, and so manufactures produce processors with more and more cores. Therefore when making software faster one way is to split task into subtasks and process them on all accessible cores at the same time. Writing parallel code is not simple and rewrite existing sequential code into parallel is even worse. Immutable data objects well fit in parallel programming, since such objects can be easily share between threads without some kind of concurrency issue (like dirty read).
In classic OOP object has state and methods manipulating with them together in one class. There are five basic principles of object oriented programming and design. In SOLID acronym the S means Single responsibility principle. This principle looks broken in classic OOP object implementation, since there are 2 reasons why to change object. First you can modify data i.e. add new property of the object, second you can modify interface i.e. add new method to update the object. I very like the approach to implement 2 types of classes, strict data objects and strict state-less objects with methods only. Then the project has:
1. plain old CLR objects (POCO), which are pure data objects. Making all these objects true immutable is a little hard in C#.
2. functional state-less objects, which implement only the methods. The principal idea (how to achieve immutability) here is that all methods taking input objects as parameters and never change their state. In cases the input object and output object are the same type, methods will always return a new data object.
There are more benefits when implementing these 2 types of classes as unit-testability (same inputs will always generate same outputs), extensibility (usually you can modify workflow without concern, that some object needs to be in exact state before calling the method), maintainability (changing the code is easier, since your algorithm behaves only on inputs), readability and understandability, and last but not least performance (with DI container we can create just single functional object and inject it everywhere it is needed).

Example

We have an application for managing relationship with customer (CRM). One of the scenario is Support Cases, or management customer issues with our product. To keep it simple, let our scenario is defined:
– As a Customer Support I create new Support Case when customer calls me a new issue.
– As a Customer Support I can modify Support Case with more additional information.
– As a Customer Support I can change status of Support Case.
– As a Customer I want to be notify when my Support Case changes the status.
Let us define simple object SupportCase

public class SupportCase
{
    public string Title { get; set; }
    public Customer Customer { get; set; }
    public SupportCaseStatus Status { get; set; }
    public IList<string> Descriptions { get; set; }
}

Now let us define interface for working with support case. Note: for this example and simplicity all methods are defined in one interface. In real application we will probably split methods into several interfaces.

public interface ISupportCaseManager
{
    SupportCase New(string title, Customer customer, string description);
    SupportCase AddDescription(SupportCase supportCase, string newDescription);
    SupportCase Open(SupportCase supportCase);
    SupportCase Close(SupportCase supportCase);
    SupportCase Cancel(SupportCase supportCase);
}

And finally let us implement Support Case manager class. Note: I have used IClonable<T> interface for cloning the SupportCase objects. And ISupportCaseNotifier for notifying customers about changing the status.

public class SupportCaseManager : ISupportCaseManager
{
    private readonly IClonable<SupportCase> _cloner;
    private readonly ISupportCaseNotifier _notifier;

    public SupportCaseManager(IClonable<SupportCase> cloner, ISupportCaseNotifier notifier)
    {
        if (cloner == null)
            throw ArgumentNullException("Argument cannot be null.", "cloner");
        if (notifier == null)
            throw ArgumentNullException("Argument cannot be null.", "notifier");
        _cloner = cloner;
        _notifier = notifier
    }

    public SupportCase New(string title, Customer customer, string description)
    {
        // validation of inputs

        // creating new support case object
        SupportCase sc = new SupportCase
        {
            Title = title,
            Customer = customer,
            Status = SupportCaseStatus.New,
            Description = new List<string>(new string[] { description })
        };

        _notifier.Notify(sc);

        return sc;
    }

    public SupportCase AddDescription(SupportCase supportCase, string newDescription)
    {
        // validation of inputs

        SupportCase sc = _cloner.Clone(supportCase);
        // we can modify new support case object in this method
        sc.Descriptions.Add(description);

        return sc;
    }

    private SupportCase ChangeStatus(SupportCase supportCase, SupportCaseStatus status)
    {
        // validation of inputs

        SupportCase sc = _cloner.Clone(supportCase);
        // we can modify new support case object in this method
        sc.Status = status;

        _notifier.Notify(sc);

        return sc;
    }

    public SupportCase Open(SupportCase supportCase)
    {
        return ChangeStatus(supportCase, SupportCaseStatus.Open);
    }

    public SupportCase Close(SupportCase supportCase)
    {
        return ChangeStatus(supportCase, SupportCaseStatus.Close);
    }

    public SupportCase Cancel(SupportCase supportCase)
    {
        return ChangeStatus(supportCase, SupportCaseStatus.Cancel);
    }
}

The immutability and proof of concurrency issue is not evident for first look. Let me extend information about notifier implementation. It is done by creating new long time running thread. It takes 5s to generate the email before it sends using SMTP server. Now let me describe what happening. Customer Support creates new support case and immediately after starts working on them by clicking on Open button. What happen in the system? It creates a new support case in New method and sends notification to customer via notifier by passing object of SupportCase. New notification thread starts generating the email using this object (remember this takes 5s). In the same second system updates the support case in Open method and sends next notification via notifier by passing object of SupportCase. New thread also starts generating the email. Now 2 threads are generating the emails of the same SupportCase, however they have different object with same data except of Status property, so emails are generated well. Note: Sending emails in right order is not described here.
In next article I will describe how to obtain immutability across the projects and system. In my applications I tried several approaches and there is not the best one. All has some pros and cons.

That’s all, now go write some code.

Leave a Reply

Your email address will not be published. Required fields are marked *