C#6: Auto-property Initializers and Expression-Bodied Properties

Last week, I discussed the new null-conditional operators added in C#6. This week, I would like to discuss two features that are awesome but could lead to some confusion: auto-property initializers and expression-bodied properties1.

Auto-initialized Properties

Before C#6, if we wanted to properly define an immutable property that had some expensive initialization, we had to do the following2:

public class MyClass
{
    public MyClass()
    {
        _immutableBackingField = System.Environment.CurrentDirectory;
    }

    public string ImmutableProperty
    {
        get
        {
            return _immutableBackingField;
        }
    }

    private readonly string _immutableBackingField;
}

Some people often use the shortcut of an auto-implemented property using the following syntax:

public class MyClass
{
    public MyClass()
    {
        ImmutableProperty = System.Environment.CurrentDirectory;
    }

    public string ImmutableProperty
    {
        get;
    }
}

However, defining properties like this means they are still mutable within the class (and its derivations). Using a backing field with the `readonly` keyword not only ensures that the property cannot be changed anywhere outside of the class construction, it also expresses exactly what you intended. Being as clear as possible is helpful for anyone who has to maintain the code in the future, including your future self.

From what I have read and heard, the main driver for using auto-implemented properties was writing less code. It somewhat saddens me when clarity of intent is replaced by speed of coding as we often pay for it later. Thankfully, both can now be achieved using initializers. Using this new feature, we can condense all that code down to just this:

class MyClass
{
    public int ImmutableProperty { get; } = System.Environment.CurrentDirectory;
}

It is a thing of beauty3. Behind the scenes, the compiler produces equivalent code to the first example with the `readonly` backing field.

Of course, this doesn't help much if you need to base your initialization on a value that is passed in via the constructor. Though a proposed feature for C#6, primary constructors, would have helped with this, it was pulled from the final release. Therefore, if you want to use construction parameters, you will still need a backing field of some kind. However, there is another feature that can help with this. That feature is expression-bodied properties4.

Expression-bodied Properties

An expression-bodied property looks like this:

class MyClass
{
    public int ImmutableProperty => 42;
}

This is equivalent to:

public class MyClass
{
    public int ImmutableProperty
    {
        get
        {
            return 42;
        }
    }
}

Using this lambda-esque syntax, we can provide more succinct implementations of our read-only properties. Consider this code:

public class MyClass
{
    public MyClass(string value)
    {
        _immutableBackingField = value;
    }

    public string ImmutableProperty
    {
        get
        {
            return _immutableBackingField;
        }
    }

    private readonly string _immutableBackingField;
}

Using expression-body syntax, we can write it as:

public class MyClass
{
    public MyClass(string value)
    {
        _immutableBackingField = value;
    }

    public string ImmutableProperty => _immutableBackingField;

    private readonly string _immutableBackingField;
}

But for the additional backing field declaration, this is almost as succinct as using an auto-implemented property. Hopefully, this new syntax will encourage people to make their intent clear rather than using the auto-implemented property shortcut when implementing immutable types.

Caveat Emptor

These new syntactical enhancements make property declaration not only easier to write, but in many common cases, easier to read. However, the similarities in these approaches can lead to some confusing, hard-to-spot bugs. Take this code as an example:

using System;

public class MyClass
{
    public string CurrentDirectory1 { get; } = Environment.CurrentDirectory;
    public string CurrentDirectory2 => Environment.CurrentDirectory;
}

Here we have two properties: `CurrentDirectory1` and `CurrentDirectory2`. Both seem to return the same thing, the current directory. However, a closer look reveals a subtle difference.

Imagine if the current directory is `C:\Stuff` at class instantiation but gets changed to `C:\Windows` some time afterward; `CurrentDirectory1` will return `C:\Stuff`, but `CurrentDirectory2` will return `C:\Windows`. The reason for this difference is the syntax used. The first property uses auto-initialization; it captures the value of `Environment.CurrentDirectory` on construction and always returns that captured value, even if `Environment.CurrentDirectory` changes. The second property uses an expression-body; it will always return the current value of `Environment.CurrentDirectory`, not the value of `Environment.CurrentDirectory` on construction of the `MyClass` instance.

I am sure you can imagine more serious scenarios where such a mix-up could be a problem. Do you think this difference in behavior will be obvious enough during code review or when a bug is reported? I certainly don't and I'm writing this as a way of reinforcing it in my own mind. Perhaps you have already dealt with a bug relating to this; if so, share your tale of woe in the comments.

In Conclusion..

I am by no means intending to discourage the use of these two additions to the C# language; they are brilliant and you should definitely add them to your coding arsenal, but like many things in software development, there is a dark side. Understanding the pros and cons of any such feature is important as it enables us to spot errors, fix bugs, and write good tests. This new confusion in the C# world is just another encouragement to code clearly, test sensibly, and be aware of the power in the tools and languages we use.

  1. No one else seems to by hyphenating "expression-bodied" but it doesn't make sense to me otherwise; what is a "bodied property"? []
  2. Yes, I know that `System.Enviroment.CurrentDirectory` isn't really expensive; this is for illustrative purposes []
  3. especially if you are keen on making sure your code expresses exactly what you mean []
  4. expression-bodied methods are also possible, but I'm not touching on that in this post []

Why software bugs exist and how you can help

Numbers in software development are represented by fixed size variables represented in binary. These are usually 8, 16, 32 or 64 bits (each bit represents a 1 or a 0). When we develop software, we choose which of these will provide enough space for the thing we are representing.

Recently, the Gangnam Style video on YouTube surpassed 2,147,483,647 views and it appeared as though the view counter broke. That number is significant because it is the upper limit of a 32-bit signed integer. It turns out that the video view count was being represented using a 32-bit signed integer — a signed value1 can represent whole numbers in the range -2,147,483,648 through 2,147,483,647; it cannot represent any number outside that range.

Though, according to YouTube, this turned out to be an Easter egg2, the bug was there before they updated the counter to 64-bit and it certainly is not the first time a number has pushed the limits. For example, the use of two digits to represent a year that contributed to the infamous fizzle that was Y2K.

But why? Why are there bugs at all?

Although it may seem like software bugs are there just to ruin your day, they were not intentionally put there or maliciously inserted to give you a reason to "Office Space" your device. As software engineers we have to consider a variety of constraints on the software we are developing. How much space does it need to run? How much space will there be on the device on which it is to run? How fast does it have to be?  How many years will the software be in use3? What other software will be running? And we have deadlines by which our work has to be completed. In fact, software engineers tend to consider a whole host of things such as the requirements of the software (functional, spatial, and temporal4), the specification of the system on which it is to execute, project deadlines and budgets, and the expectations of the end user.

Almost always, there has to be compromise. Even though a solution might be possible that accommodates all considerations, we have to deliver software in a time frame that people will pay for and some things just take too long or cost too much. That is not to say that all software bugs are because of time and money, some exist because of mistakes and as a consequence of poor design.

What does it all mean?

Software engineers like myself do not want you to encounter bugs. We work very hard and the QA teams work very hard to ensure that you do not get buggy software; if we see a problem, we do what we can to address it. To find these bugs, we try to consider all the ways our software might be used and test them. Unfortunately, most bugs do not advertise their existence quite so obviously or politely as YouTube's view counter bug. For example, when in comes to the infinite ways any one device might be configured with different peripherals and apps installed, we just cannot test them all; the system is too complex5.

 Good developers welcome user feedback. We need your help.

Next time you encounter a bug in the software you use, whether it is a mobile app, a website, an ATM, a desktop application, or some other device, spare a thought for the software engineers. Remember, good developers welcome user feedback. Take a moment to tell them or the publishers of the software about the problem.

  • What did you type, touch, or click?
  • What else was running on your device?
  • What part of the software were you in?
  • What Internet browser or operating system were you running?
  • What versions?

Be as detailed as you can. All these details and more can help a software engineer track down, reproduce and ultimately fix that bug.

There is no malice in a software bug. It was not put there specifically to ruin your day. However, without your help, it will not go away. So, reach out to the developers and tell them, they will thank you for it.

Today's featured image is based on Software Bugs by Martin Maciaszek.

  1. signed means it can be positive or negative []
  2. An Easter Egg is a hidden feature often added to software as an amusement for users who find it. Examples include the "barrel roll" in Google or the flight simulator in Excel []
  3. Underestimation of this was a big contributor to Y2K []
  4. i.e. how it works, what space it needs, and how long it takes to run and for how long it has to run []
  5. That's a discussion for another time []