Lazy props for the lazy!
Davyd McColl
Posted on November 22, 2019
What is lazy evaluation?
From Wikipedia:
In programming language theory, lazy evaluation, or call-by-need is an
evaluation strategy which delays the evaluation of an expression until its
value is needed and which also avoids repeated evaluations
Why be lazy?
As per the description above, lazy evaluation results in less up-front cost and is highly beneficial when writing code where not all paths will be immediately exercised and one would like to provide a friendlier API for consuming code.
For example, consider code which has to fetch values from the database:
public interface ICustomerRepository
{
OrderAddress[] FetchCustomerOrderAddresses(int customerId);
}
public class CustomerModel
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public OrderAddress[] OrderAddresses
{
get
{
if (_orderAddresses == null)
{
_orderAddresses = FetchOrderAddresses();
}
return _orderAddresses;
}
}
private OrderAddress[] _orderAddresses;
private readonly ICustomerRepository _customerRepository;
public CustomerModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
private OrderAddress[] FetchOrderAddresses()
{
return _customerRepository.FetchCustomerOrderAddresses(
CustomerId
);
}
}
We might have such a convenience object which we would only sometimes interrogate for recent order addresses (as one of the steps on checkout, for example). The _customerRepository
would do a database request, inspecting prior orders from the customer and returning the unique set of addresses we've delivered to before.
Since we only need those addresses sometimes, we'd perfer not to always incur the cost of fetching them. On the other hand, we also don't want to fetch from the database multiple times by accident. So we use a backing field and a lazy getter. Nothing too exciting here.
Enter the null-coalescing operator (C# 2)
If you aren't familiar with it, the null-coalescing operator (??
) provides a simpler syntax for the OrderAddresses
property above:
public OrderAddress[] OrderAddresses =>
_orderAddresses ?? (_orderAddresses = FetchOrderAddresses());
When this property is read, the _orderAddresses
field is checked for null
. If not null, it is returned, otherwise the result of
(_orderAddresses = FetchOrderAddresses());
is returned. C#, like many other languages, returns the value of the assignment when making an assignment. So this statement above not only assignes the customers order addresses to the _orderAddresses
field, it also returns that value.
Side-note: the ??
operator can also be used as shorthand for throwing an exception when the left-hand-side value is null, for example:
public OrderAddress[] FetchOrderAddresses(int customerId)
{
return DatabaseRequestForAddressesByCustomerId(customerId)
?? throw new CustomerHasNoSavedAddressesException(customerId);
}
Now back to your regular programming!
Today I've been recommended by Rider to use the new null-coalescing assignment operatorhttps://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator).
Null-what-now?
As someone who makes use of the lazy property pattern a lot, I was quite pleased to see this new syntax which Rider suggested and then fixed in-place for me:
public OrderAddress[] OrderAddresses =>
_orderAddresses ??= FetchOrderAddresses();
This works exactly like the examples above, but is really quick to write and (I think) just as clear. This operator comes with C# 8.0
But I don't have C# 8.0? Do I have to get the latest Visual Studio or Rider?
Here's the really great news: Roslyn is really, really smart -- and pluggable. One of the questions I ask prospective hires is "how can I get the latest C# features in my IDE?". Most say that I need to update my IDE or system-wide tooling, but you really don't
Because of the pluggable nature of Roslyn, you just need to add the package:
Microsoft.Net.Compilers
to your project and add this to your .csproj file:
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
(if you already have a LangVersion
node in one or another property group(s), you can modify there -- but having a single PropertyGroup
for this setting means you don't have to specify it for each build configuration in your .csproj.
With those two requirements met, you may now write C# with all of the latest and greatest features -- and there are plenty more beyond ??=
.
Posted on November 22, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.