NDepend

Improve your .NET code quality with NDepend

C# Immutable Types Understanding the Attraction

C# Immutable Types: Understanding the Attraction

In our post about value objects, we briefly covered the topic of immutability. There, we talked about the more conceptual side of immutability, as in “value objects must be immutable types since it wouldn’t make sense for them to be otherwise.”

As it turns out, there are many practical benefits to making your types immutable—not just the value objects. I remember the first time I came across the concept of immutable types. I was researching string immutability, and somehow Google led me to a Stack Overflow question about immutability in general.

Up until that moment, I wasn’t familiar with the concept of immutability. I must admit it wasn’t an easy idea to wrap my head around. I remember thinking, “Why would I want objects that don’t change? Things change in real life!”

So, I’m writing this post with my past self in mind. The goal here is twofold: to explain why immutable types are desirable and to provide a quick recipe on how to work with them.

Immutable Types: What Are They?

Put simply, immutable types (objects or data structures) are types that, once initialized, cannot change their internal state. That’s it.

Of course, there can be much more to it than that. I’ll get back to that before the post is over. But for now, I think this definition is enough for us.

A classic example of an immutable type in .NET is the System.DateTime. Once you have a DateTime, you can never change it. When you use methods such as “AddDays,” you get a new DateTime, which is the result of the operation.

The Benefits of Immutable Types

There’s no shortage of posts out there singing immutability’s praises. Now that we’ve briefly defined what immutable types are, the next question is “Why are they A Good Thing“?  

They’re Easier To Reason About

A piece of code is said to be easy to reason about if it’s predictable: if it fulfills the promises it makes to its clients, or in other words, if it adheres to the principle of least astonishment. And in what way do immutable types show this quality?

Simple. Since they never change, they can never be accidentally altered in a way that would lead to undesirable effects. For a more concrete example, let’s do a quick thought exercise using an already mentioned type: System.DateTime. Imagine the newest version of .NET comes with a new type called “MutableDateTime.” It’s essentially the same as a regular DateTime except for two things. First, it’s a class instead of a struct. (In other words, it’s a reference type instead of a value type.) Second, it’s mutable, meaning methods such as “AddDays” change the object in place instead of returning a new instance.

Now, you’ll have to pardon me for invoking the ultimate programming blog post cliché, but I’m going to do it. I’m going to use a “Person” class as an example. Consider the following code:

Now, I retrieve a person instance from the database:

And for whatever reason, I need to know the date of the day 10 days after the date of birth. So, I do this:

Now, considering that the date of birth of the person retrieved from the database was, let’s say, December 7, 1989, what would the following line print?

If you answered “December 17,” you’re right. That may sound like a silly example, but something extremely similar to this caused a lot of problems in a certain mainstream language that I won’t name.

Code like this can cause extremely nasty bugs. But that can all be avoided if the types are created immutable.

Thread-Safety

This is, essentially, a consequence of the previous benefit. If immutable types can’t be changed at all, they can’t be changed by another thread, which makes them safe to be shared. When some piece of code needs to change the value, it won’t actually change anything; it will create a new instance with the updated values.

No Need For Defensive Copying

This is also a consequence of the first point. Since immutable types will never change, you don’t need to be afraid of them being changed accidentally. Thus, you don’t have to come up with strategies to prevent that from happening, such as defensive copying/cloning.

Immutable Types Are Always in a Valid State

I could’ve also named this subtopic “they guarantee their invariants at all time.” The idea here is very simple. If you combine immutability with object validation at creation time, then you’re guaranteed to have a valid object for that point on. Here’s a quick example:

C# Immutable Types: an Implementation Recipe

All this talk can leave you with the impression that creating an immutable is something of a sophisticated task. I’d say that, yes, it is a sophisticated approach. But that doesn’t mean it’s hard to do.

Consider the code of the following “RGBColor” class:

In real life, such a class would probably have overrides for “ToString,” “Equals,” “GetHashCode,” and the like, and probably some more methods relevant to its domain. I didn’t include any of this out of simplicity. In real life, you’d also probably use byte instead of int, but I’ve chosen int for a specific reason, and I’ll get back to that. So what about this design?

Well, this class is not only mutable—it’s embarrassingly mutable. It has automatic properties for both reading and writing and optional parameters that enable the caller to omit the values for any of the component colors.

So, how do we turn this into an immutable type? First step: make the setters private.

Now the object can’t be changed from the outside. It can still be changed from inside the class, though. So let’s get rid of the setters for good and implement readonly backing fields. While we’re at it, let’s change the constructor to make it use the fields and turn the optional parameters into required ones:

And that’s pretty much it. We can summarize what we did in three simple steps:

  1. Remove setters.
  2. Turn the private fields into readonly (or create them, if they don’t exist at this point).
  3. Change the constructor so it requires all needed parameters at construction time.

Easy, isn’t it?

There’s one more thing we need to do, though, and this has to do with validation. We must ensure that only values in the range of 0 to 255 are allowed. The current design allows you to pass any arbitrary integers as parameters, and we don’t want that. So it’d probably make sense to add guard clauses to verify that the parameters fall into the correct range and throw otherwise. I’ll leave that as an exercise for the reader.

Why Isn’t Everything Immutable?

If immutable types are so awesome, then why isn’t everything immutable? That’s actually a very good question, and it’s one that’s been asked quite a lot before. One of the most often-cited pitfalls of immutable types is performance. The overhead generated by creating new objects might have an impact on the performance of your application.

Sometimes, mutability is just the most intuitive mental model for an application. Think of GUI programming or games.

In the end, many (maybe most) developers just use mutable types because they’re the default in the mainstream OOP languages.

The Only Constant Is Change…Unless We’re Talking About Your Types

The goal of this post was to introduce you to the basics of immutability. There’s definitely a lot more to immutable types than this. Eric Lippert, for instance, has a wonderful article about the different kinds of immutability.

The use of immutable types offer some very nice benefits, but it’s no silver bullet. Everything in software development is a tradeoff, and it’s part of your job as a developer to weigh the pros and cons of different approaches and figure out what makes more sense for your project and team.

That being said, I do suggest you to give immutability a serious attempt. Maybe you can adopt an “immutable-first approach”: everything is immutable by default until there’s a very good reason to change.

And of course, don’t forget to research how you can take advantage of NDepend to enforce immutability in your projects.

Contributing Author

Carlos Schults is a .NET software developer with experience in both desktop and web development, and he’s now trying his hand at mobile. He has a passion for writing clean and concise code, and he’s interested in practices that help you improve app health, such as code review, automated testing, and continuous build. You can read more from Carlos at carlosschults.net.

Comments:

  1. Here’s a more succinct example of the final RGBColor implementation! Properties that use { get; } are readonly by default.

    public class RGBColor
    {
    public int Red { get; }

    public int Green { get; }

    public int Blue { get;}

    public RGBColor(int red, int green, int blue)
    {
    Red = red;
    Green = green;
    Blue = blue;
    }
    }

  2. What is the benefit of having backing fields for read-only properties? In other words, why not doing this:

    public int Red {get;}

    public int Green {get;}

    public int Blue {get;}

    public RGBColor(int red, int green, int blue)
    {
    this.Red = red;
    this.Green = green;
    this.Blue = blue;
    }

  3. Nice.

    Don’t write “put simply” – it and other similar phrases are verbiage. Just leave them out.

    To improve your writing – ie to make it far more concise – I recommend “Politics and The English Language” by George Orwell. Writing Prose, and On Writing Well.

    cheers.

  4. “The Benefits of Immutable Types”: totally agree!

    “C# Immutable Types: an Implementation Recipe”: totally agree with the other comments that you should use C# 6’s read-only auto properties public int Red { get; }

    “Why Isn’t Everything Immutable?”: True, and I’d add, never pre-optimize, and always pinpoint the real bottleneck with a performance profiler before sacrificing maintainability for performance. Also “Sometimes, mutability is just the most intuitive mental model for an application” — this is arguable, but true for us who have not been doing FP for some time. So I’d say, continue striving for more immutability (and more functional programming in general). This adds more benefits of less error-proneness through state, easier testability, and more readable, maintainable code (provided you use good naming, as with any code).

Leave a Reply

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