C# 7.0 introduces throw expressions. We can add exception throwing to expression-bodied members, null-coalescing expressions and conditional expressions. This blog post introduces throw expressions, demonstrates how to use them and also provides a peek behind a compiled throw expressions.
Throw expressions are the way to tell compiler to throw exception under specific conditions like in expression bodied members or inline comparisons. Before going to some practical use cases let’s see some illustrative examples of throw expressions that were originally posted with .NET Blog post What’s New in C# 7.0
class Person
{
public string Name { get; }
public Person(string name) => Name = name ?? throw new ArgumentNullException(name);
public string GetFirstName()
{
var parts = Name.Split(' ');
return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
}
public string GetLastName() => throw new NotImplementedException();
}
Now let’s see what we can do with throw expressions in practice.
Throw expressions in unit testing
We can use throw expressions when writing non-functioning methods and properties we plan to cover with tests. As these members usually throw NotImplementedException we can save some space here.’’
public class Customer
{
// ...
public string FullName => throw new NotImplementedException();
public Order GetLatestOrder() => throw new NotImplementedException();
public void ConfirmOrder(Order o) => throw new NotImplementedException();
public void DeactivateAccount() => throw new NotImplementedException();
}
Of course, this example has also its dark side. It hides the fact that the class can grow too big and there can be need to split it to smaller pieces.
Keeping setters short
Using throw expressions we can keep some setters short. Here is the FirstName property of Person class.
private string _firstName;
public string FirstName
{
get => _firstName;
set => _firstName = value ?? throw new ArgumentNullException();
}
Notice that throw expressions work also with null-coalescing operator (??).
NB! This example is not an example of bets practices. It applies to properties that actually need body with some logic. If automatic properties and validation rules match your needs then go with these. Don’t change your code to use throw expressions just because they are here or they are looking cool.
Throw expressions in action
Here is the code example where some method arguments are compared to null.
public Order AddProductToNewOrder(Product product, Customer customer, double amount)
{
if(product == null)
{
throw new ArgumentNullException(nameof(product));
}
if(customer == null)
{
throw new ArgumentNullException(nameof(customer));
}
var orderLine = new OrderLine();
orderLine.Product = product;
orderLine.Amount = amount;
var order = new Order();
order.Customer = customer;
order.AddLine(orderLine);
return order;
}
We get this code shorter when using throw experssions. Actually Visual Studio proposes this change for us.
Here is the code with null check simplifications and some well-hidden land mines.
public Order AddProductToNewOrder(Product product, Customer customer, double amount)
{
var orderLine = new OrderLine();
orderLine.Product = product ?? throw new ArgumentNullException(nameof(product));
orderLine.Amount = amount;
var order = new Order();
order.Customer = customer ?? throw new ArgumentNullException(nameof(customer));
order.AddLine(orderLine);
return order;
}
As far creating order and order line is creating new instance of simple class we are safe to go with this code. I don’t like the fact that objects are created. If new instances of order and order line are created using factory classes we cannot use the code above as creating these instances may be resource consuming or complex activity. Factory classes are usually not introduced for fun.
Behind the compiler
Let’s take now the FirstName property shown above and see what compiler produces of it. Without any guidance by PDB-file JetBrains dotPeek decompiles the property for us this way.
private string _firstName;
public string FirstName
{
get
{
return this._firstName;
}
set
{
string str = value;
if (str == null)
throw new ArgumentNullException();
this._firstName = str;
}
}
As you can see then throw expression is just a nice addition to language and it doesn’t have any meaning in runtime level. Without PDB-file telling decompiler the truth about the code in library we get back what was compiled.
Wrapping up
Throw expressions are here to help us write smaller code and use exceptions in expression-bodied members. It is just a language feature and not anything fundamental in language runtime. Although throw expressions help us to write shorter code it is not a silver bullet or cure for every disease. Use throw expressions only when they can help you.
View Comments (3)
Why does the compiler creates a second reference like this:
string str = value;
if (str == null)
throw new ArgumentNullException();
this._firstName = str;
instead of this?
if (value == null)
throw new ArgumentNullException();
this._firstName = value;
This is interesting question. Not sure why code is like this. Probably it makes local copy of string to avoid changing value of the one given to setter method.
Is it possible to use throw expressions as argument defaults somehow - to cause the checking to happen at the start of the function?
ie something along these lines :
public Order AddProductToNewOrder(
Product product = throw new ArgumentNullException(nameof(product)),
Customer customer = throw new ArgumentNullException(nameof(customer)),
double amount)
{
var orderLine = new OrderLine();
orderLine.Product = product;
orderLine.Amount = amount;
var order = new Order();
order.Customer = customer;
order.AddLine(orderLine);
return order;
}
Thank you