📜 ⬆️ ⬇️

Throw expressions in C # 7

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.


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) // concise inline LINQ assertion with .Select! .Select(c => c.Status == Status.None ? throw new InvalidDataException($"Customer {c.Id} has no status and should not be an award recipient") : c) .ToList(); 

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 { // ... 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(); } 

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.

Source: https://habr.com/ru/post/348658/


All Articles