With C# 8 on our doorstep, I figure it is a good time to reflect on recent additions to the language that have come before. There are some great improvements you may have missed, some that I really enjoy using, and some I consider have reached canonical usage status that I think are all worth some reflection.
Throwing Exceptions is both a blessing and a curse in C#. You can throw from anywhere to unwind the stack. But adding exception throwing and handling code can cause overheads that you need to be wary of. Not to mention making the code harder to reason about with control flow.
Regardless, we have them, and we use them, so the language should make that as easy as possible to do so. with C# 7 this was enhanced to support using throw
as an expression. Up until this point, it was always a statement, which means there are places in the code that throw could not go.
One new language feature that only supports expressions and not statements is the Elvis Operator (or Null Conditional Operator, which we already looked back at) and another is the null coalescing operator. When it comes to validating code, these two features shine together.
public MyClass(string firstArg, string secondArg, Widget widget)
{
if(firstArg is null)
{
throw new ArgumentNullException(nameof(firstArg));
}
if(secondArg is null)
{
throw new ArgumentNullException(nameof(secondArg));
}
if(widget is null)
{
throw new ArgumentNullException(nameof(widget));
}
_firstArg = firstArg;
_secondArgLength = secondArg.Length;
_widget = widget;
}
So verbose! wouldn’t it be nice to collapse it together a bit? With throw expressions, we can.
public MyClass(string firstArg, string secondArg, Widget widget)
{
_firstArg = firstArg ?? throw new ArgumentNullException(nameof(firstArg));
_secondArg = secondArg?.Length ?? throw new ArgumentNullException(nameof(secondArg));
_widget = widget ?? throw new ArgumentNullException(nameof(widget));
}
So much shorter and more concise. This saves typing time and reading the time. The magic is that the right-hand-side of the null coalescing operator has to take an expression that evaluates to the right type. Because throw is now an expression, it can do exactly that and satisfy the compilation.
Also remember how we looked at Expression body members in C# 6 and more in c# 7? Well as the name suggests, the body is an expression. Now that we have throw expressions, the bodies can use them too!
public class Widget
{
public Widget() => throw new ArgumentException("Requires arguments, none provided.");
public ~Widget() => throw new NotSupportedException("What are you building that needs a destructor, anyway? (Just curious.)");
public string MyValueProperty => throw new NotImplementedException();
public decimal CalculateTheAnswer() => throw new NotImplementedException();
private T[] _items = new T[100];
public T this[int i]
{
get => _items?[i] ?? throw new Exception("Not initialised somehow.");
set => throw new NotSupportedException("An interface made me do this.");
}
}
There are other use-cases out there, expressions are used all over the place. But I will leave that as an exercise to the reader to discover along the way.