Hello. We continue to explore the new features of C # 7. We have already considered such topics as:
pattern matching ,
local functions ,
tuples . Today we will talk about
Throw .
In C #,
throw has always been an operator. Because
throw is an operator, not an expression, there are constructs in C # that cannot be used.
- in the operator Null-Coalescing (??)
- in lambda expression
- in a conditional operator (? :)
- in the body of expressions ( expression-bodied )
To correct these problems, C # 7 introduces
throw expressions. The syntax is the same as used for throw statements. The only difference is that they can now be used in a large number of cases.
Let's look at the places where it will be better to use the
expression . Go!
Ternary operators
Up to version 7 of the C # language, the use of
throw in the ternary operator was forbidden, since it was an
operator . In the new version of C #,
throw is used as an
expression , therefore we can add it to the ternary operator.
')
var customerInfo = HasPermission() ? ReadCustomer() : throw new SecurityException("permission denied");
Error message when checking for null
“The object reference does not indicate an object instance” and “The
Nullable object should be set to value” are the two most common errors in C # applications. Using
throw expressions makes it easier to give a more detailed error message:
var age = user.Age ?? throw new InvalidOperationException("user age must be initialized");
Displaying an Error Message in the Single () Method
In the process of dealing with errors of checks for
null , in the logs you can see the most common and useless error message: "The sequence does not contain elements." With the advent of
LINQ , C # programmers often use the
Single () and
First () methods to find an item in a list or query. Although these methods are brief, when an error occurs, they do not provide detailed information about which statement was violated.
Throw expressions provide a simple template for adding complete error information without compromising brevity:
var customer = dbContext.Orders.Where(o => o.Address == address) .Select(o => o.Customer) .Distinct() .SingleOrDefault() ?? throw new InvalidDataException($"Could not find an order for address '{address}'");
Error message during conversion
In C # 7, type templates offer new ways of casting. Using
throw expressions, you can provide specific error messages:
var sequence = arg as IEnumerable ?? throw new ArgumentException("Must be a sequence type", nameof(arg)); var invariantString = arg is IConvertible c ? c.ToString(CultureInfo.InvariantCulture) : throw new ArgumentException($"Must be a {nameof(IConvertible)} type", nameof(arg));
Expressions in the body of methods
Throw expressions offer the most concise way to implement an error-throw method:
class ReadStream : Stream { ... override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException("read only"); ... }
Dispose Check
Well-managed
IDisposable classes throw
ObjectDisposedException on most operations after they are removed.
Throw expressions can make these checks more convenient and less cumbersome:
class DatabaseContext : IDisposable { private SqlConnection connection; private SqlConnection Connection => this.connection ?? throw new ObjectDisposedException(nameof(DatabaseContext)); public T ReadById(int id) { this.Connection.Open(); ... } public void Dispose() { this.connection?.Dispose(); this.connection = null; } }
LINQ
LINQ provides the ideal setting to combine many of the above uses. Since it was released in the third version of C #,
LINQ has changed the programming style to C # in the direction of expression-oriented, not operator-oriented. Historically,
LINQ has often forced developers to make compromises between adding meaningful statements and excluding them from code, while remaining in the syntax of a compressed expression that works best with
lambda expressions.
Throw expressions solve this problem!
var awardRecipients = customers.Where(c => c.ShouldReceiveAward)
Unit testing
Also,
throw expressions are well suited for writing non-working methods and properties (stubs) that are planned to be covered with tests. Since these members usually throw
NotImplementedException , you can save some space and time.
public class Customer {
Typical check in the constructor
public ClientService( IClientsRepository clientsRepository, IClientsNotifications clientsNotificator) { if (clientsRepository == null) { throw new ArgumentNullException(nameof(clientsRepository)); } if (clientsNotificator == null) { throw new ArgumentNullException(nameof(clientsNotificator)); } this.clientsRepository = clientsRepository; this.clientsNotificator = clientsNotificator; }
Everyone is too lazy to write as many lines of code to check, now, if we use the features of C # 7, we can write expressions. This will allow you to rewrite such code.
public ClientService( IClientsRepository clientsRepository, IClientsNotifications clientsNotificator) { this.clientsRepository = clientsRepository ?? throw new ArgumentNullException(nameof(clientsRepository)); this.clientsNotificator = clientsNotificator ?? throw new ArgumentNullException(nameof(clientsNotificator)); }
You should also say that
throw expressions can be used not only in the constructor, but also in any method.
Property Setters
Throw expressions also allow you to make object properties shorter.
public string FirstName { set { if (value == null) throw new ArgumentNullException(nameof(value)); _firstName = value; } }
You can make it even shorter using the
Null-Coalescing (??) operator.
public string FirstName { set { _firstName = value ?? throw new ArgumentNullException(nameof(value)); } }
or even use an expression body for access methods (getter, setter)
public string FirstName { set => _firstName = value ?? throw new ArgumentNullException(nameof(value)); }
Let's see what this code is being developed by the compiler:
private string _firstName; public string FirstName { get { return this._firstName; } set { string str = value; if (str == null) throw new ArgumentNullException(); this._firstName = str; } }
As we can see, the compiler itself led to the version we wrote at the very beginning of the paragraph. Therefore, it is not necessary to write extra code, the compiler will do it for us.
Conclusion
Throw expressions help write smaller code and use exceptions in
expression-bodied expressions . This is just a language function, and not something major in the language execution environment. Although
throw expressions help write shorter code, it is not a silver bullet or a cure for all diseases. Use
throw expressions only when they can help you.