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 properties.
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 following:
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 beauty. 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 properties.
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.