Pull to refresh

Throw выражения в C# 7

Reading time5 min
Views24K
Всем привет. Продолжаем исследовать новые возможности C# 7. Уже были рассмотрены такие темы как: сопоставление с образцом, локальные функции, кортежи. Сегодня поговорим про Throw.

В C# throw всегда был оператором. Поскольку throw — это оператор, а не выражение, существуют конструкции в C#, в которых нельзя использовать его.

  • в операторе Null-Coalescing (??)
  • в лямбда выражении
  • в условном операторе (?:)
  • в теле выражений (expression-bodied)

Чтобы исправить данные проблемы, C# 7 вводит выражения throw. Синтаксис остался таким же, как всегда использовался для операторов throw. Единственное различие заключается в том, что теперь их можно использовать в большом количестве случаев.
Давайте рассмотрим, в каких местах throw выражения будет лучше использовать. Поехали!

Тернарные операторы


До 7 версии языка C#, использование throw в тернарном операторе запрещалось, так как он был оператором. В новой версии С#, throw используется как выражение, следовательно мы можем добавлять его в тернарный оператор.

var customerInfo = HasPermission()
? ReadCustomer()
: throw new SecurityException("permission denied");

Вывод сообщения об ошибке при проверке на null


«Ссылка на объект не указывает на экземпляр объекта» и «Объект Nullable должен иметь значение», являются двумя наиболее распространенными ошибками в приложениях C#. С помощью выражений throw легче дать более подробное сообщение об ошибке:


var age = user.Age ?? throw new InvalidOperationException("user age must be initialized");

Вывод сообщения об ошибке в методе Single()


В процессе борьбы с ошибками проверок на null, в логах можно видеть наиболее распространенное и бесполезное сообщение об ошибке: «Последовательность не содержит элементов». С появлением LINQ, программисты C# часто используют методы Single() и First(), чтобы найти элемент в списке или запросе. Несмотря на то, что эти методы являются краткими, при возникновении ошибки не дают детальной информации о том, какое утверждение было нарушено.

Throw выражения обеспечивают простой шаблон для добавления полной информации об ошибках без ущерба для краткости:

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}'");

Вывод сообщения об ошибке при конвертации


В C# 7 шаблоны типа предлагают новые способы приведения типов. С помощью выражений throw, можно предоставить конкретные сообщения об ошибках:

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));

Выражения в теле методов


Throw выражения предлагают наиболее сжатый способ реализовать метод с выбросом ошибки:

class ReadStream : Stream
{
  ...
  override void Write(byte[] buffer, int offset, int count) =>
  throw new NotSupportedException("read only");
  ...
}

Проверка на Dispose


Хорошо управляемые классы IDisposable бросают ObjectDisposedException на большинство операций после их удаления. Throw выражения могут сделать эти проверки более удобными и менее громоздкими:

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 обеспечивает идеальную настройку, чтобы сочетать многие из вышеупомянутых способов использования. С тех пор, как он был выпущен в третьей версии C#, LINQ изменил стиль программирования на C# в сторону ориентированного на выражения, а не на операторы. Исторически LINQ часто заставлял разработчиков делать компромиссы между добавлением значимых утверждений и исключений их из кода, оставаясь в синтаксисе сжатого выражения, который лучше всего работает с лямбда выражениями. Throw выражения решают эту проблему!

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 тестирование


Также, throw выражения хорошо подходят при написании неработающих методов и свойств (заглушек), которые планируются покрыть с помощью тестов. Поскольку эти члены обычно бросают NotImplementedException, можно сэкономить некоторое место и время.


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

Типичная проверка в конструкторе



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;
}

Всем лень писать столько строчек кода для проверки, теперь, если использовать возможности C# 7, можно написать выражения. Это позволит вам переписать такой код.

public ClientService(
IClientsRepository clientsRepository,
IClientsNotifications clientsNotificator)
{
  this.clientsRepository = clientsRepository ?? throw new ArgumentNullException(nameof(clientsRepository));
  this.clientsNotificator = clientsNotificator ?? throw new ArgumentNullException(nameof(clientsNotificator));
}

Также следует сказать, что throw выражения можно использовать не только в конструкторе, но и в любом методе.

Сеттеры свойств


Throw выражения также позволяют сделать свойства объектов более короткими.


public string FirstName
{
  set
  {
    if (value == null)
      throw new ArgumentNullException(nameof(value));
    _firstName = value;
  }
}

Можно сделать еще короче, используя оператор Null-Coalescing (??).

public string FirstName
{
  set
  {
    _firstName = value ?? throw new ArgumentNullException(nameof(value));
  }
}

или даже использовать тело выражения для методов доступа (геттер, сеттер)

public string FirstName
{
  set => _firstName = value ?? throw new ArgumentNullException(nameof(value));
}

Давайте посмотрим, во что разворачивается данный код компилятором:

private string _firstName;
public string FirstName
{
   get
   {
     return this._firstName;
   }
   set
   {
     string str = value;
     if (str == null)
        throw new ArgumentNullException();
     this._firstName = str;
   }
}

Как мы видим, компилятор сам привел к той версии, которую мы писали в самом начале пункта. Следовательно, не надо писать лишний код, компилятор сделает это за нас.

Заключение.


Throw выражения помогают писать меньший код и использовать исключения в выражениях-членах (expression-bodied). Это всего лишь языковая функция, а не что-то основное в языковой среде исполнения. Хотя throw выражения помогают писать более короткий код, это не серебряная пуля или лекарство от всех болезней. Используйте throw выражения только тогда, когда они могут вам помочь.
Tags:
Hubs:
+26
Comments22

Articles