C#7で式をスローする

みなさんこんにちは。 C#7の新機能を引き続き検討します。 パターンマッチングローカル関数タプルなどのトピックは既に取り上げています。 今日はThrowについて話しましょう。



C#では、 throwは常に演算子でした。 throwは式ではなく演算子であるため、C#には使用できない構造があります。





これらの問題を修正するために、C#7はthrow式を導入しています。 構文は、throwステートメントに常に使用されるものと同じままです。 唯一の違いは、多くのケースで使用できるようになったことです。

スロー式を使用した方が良い場所を見てみましょう。 行こう!



三項演算子



C#言語のバージョン7より前は、三項演算子でのthrowの使用は演算子であったため禁止されていました。 新しいC#バージョンでthrowは として使用されるため、三項演算子に追加できます。



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





nullチェック時のエラーメッセージ



「オブジェクト参照はオブジェクトのインスタンスを示すものではありません」と「 Nullableオブジェクトには値が必要です」は、C#アプリケーションで最もよく見られる2つのエラーです。 throw式を使用すると、より詳細なエラーメッセージを簡単に表示できます。



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





Single()メソッドで出力されるエラーメッセージ



nullチェックのエラーに対処する過程で、ログに最も一般的で役に立たないエラーメッセージ「シーケンスに要素が含まれていません」が表示されます。 LINQの出現により、C#プログラマーは、 Single()およびFirst()メソッドを使用してリストまたはクエリ内のアイテムを見つけることがよくあります。 これらのメソッドは簡潔ですが、エラーが発生した場合、違反したステートメントに関する詳細な情報は提供しません。



スロー式は、簡潔さを犠牲にすることなく、完全なエラー情報を追加するためのシンプルなテンプレートを提供します。



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





メソッド本体の式



スロー式は、エラーをスローするメソッドを実装する最も簡潔な方法を提供します。



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





廃棄の確認



適切に管理されたIDisposableクラスは、削除された後、ほとんどの操作でObjectDisposedExceptionをスローします。 スロー式を使用すると、これらのチェックがより便利になり、扱いにくくなります。



 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#の3番目のバージョンでリリースされて以来、 LINQはプログラミングスタイルをC#からステートメントベースではなく式指向に変更してきました。 歴史的に、 LINQは意味のあるステートメントの追加とコードからの除外との間で妥協することを開発者にしばしば強制し、 ラムダ式で最適に機能する圧縮式の構文のままです。 スロー式はこの問題を解決します!



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





単体テスト



また、 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)); }
      
      





また、 スロー式はコンストラクターだけでなく、任意のメソッドでも使用できると言わなければなりません。

プロパティセッター



スロー式を使用すると、オブジェクトのプロパティを短くすることもできます。



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





ご覧のとおり、コンパイラ自体が、段落の最初に書いたバージョンにつながりました。 したがって、余分なコードを記述しないでください。コンパイラーがそれを行います。



おわりに



スロー式は、小さなコードを記述し、式式で例外を使用するのに役立ちます( expression-bodied )。 これは単なる言語機能であり、言語ランタイムの基本的なものではありません。 スロー式は短いコードを書くのに役立ちますが、それはすべての病気の特効薬または治療法ではありません。 throw式は、役立つ場合にのみ使用してください。



All Articles