resource-cleanupでは、IDisposeインターフェースを使って、オブジェクトのリソースやその他のプログラムの状態を、そのオブジェクトが不要になったときに解放またはリセットする必要があること(そして、ガベージコレクターに頼ってもこれを実現できず、必要なレベルの制御も得られないこと)を伝えます。そして、そうしたクリーンアップ処理を行うのに自然な場所がIDisposable.Dispose()メソッドです。
これとは別に、usingブロックという構文があります。これは、呼び出し側から見ると、リソースの生存期間の管理をすべて1つの文にまとめることを可能にします。
using (var file = new File("my_secrets"))
{
file.WriteSecret("xxxxxxx");
}
上の例では、fileに関連付けられたファイルシステムのリソースは、usingブロックを抜けるときにオペレーティングシステムに返却されます。
このパターンが機能するには、using文の変数がIDisposableインターフェースを実装する参照型であり、そのDispose()メソッドでリソースの解放やプログラム状態のリセットを行わなければなりません。
C# 8では、このパターンがさらに改良されました。using文をブロックの先頭に置けるようになったのです。
using var drawingResource = some_provided_or_new_object;
try
{
drawingResource.DrawSomething();
}
catch (Exception)
{
throw;
}
Javaの開発者なら、これはJava 7で導入された_自動リソース管理_の仕組みに似ていると気づくかもしれません。
object-relational-mappingで紹介したORM(オブジェクト関係マッピング)システムに、再び取り組むことになりました。
ORMの利用状況を分析したところ、トランザクションの95%は1つの呼び出しメソッドの中から実行されていることがわかりました。そこで、トランザクションを開始し、書き込み、コミットする処理を1つのORMメソッドにまとめるほうが適切だということになりました。
データベースには次のインスタンスメソッドがあります。
Database.BeginTransaction()は、データベースでトランザクションを開始します。Database.Write(string data)は、トランザクション内でデータベースにデータを書き込みます。不正なデータを受け取ると、例外がスローされます。BeginTransaction()を呼び出さずにこのメソッドを呼び出そうとすると、例外がスローされます。成功すると、データベースの内部状態はDataWrittenに変わります。Database.EndTransaction()は、トランザクションをデータベースにコミットします。トランザクションを閉じられない場合や、Database.BeginTransaction()が呼び出されていなかった場合は、例外をスローすることがあります。Database.Dispose()を呼び出すと、トランザクション中に例外がスローされた場合にデータベースをクリーンアップします。これにより、データベースの状態はClosedに変わります。Orm.Write()メソッドを実装して、データベースのトランザクションを開始し、渡されたデータを書き込み、トランザクションをコミットしてください。InvalidOperationExceptionを含むすべての例外は、ログに記録したりその他の処理を加えたりせず、そのまま呼び出し元に渡すようにします。
Database db = new Database();
Orm orm = new Orm(db);
orm.Write("good write");
// => database has an internal state of State.Closed
orm.Write("bad write");
// => an exception is thrown but database is left with an internal state of State.Closed
orm.Write("bad commit");
// => an exception is thrown but database is left with an internal state of State.Closed
Orm.Write()が導入されてから数か月が経ち、チーム内では意見が分かれています。多くのエンジニアは、例外がスローされたときに_ORM_側でエラーを処理し、成功したかどうかの印だけを呼び出し元に返してほしいと考えています。
Orm.WriteSafely()メソッドを実装して、(上記と同様に)データベースのトランザクションを開始し、渡されたデータを書き込み、トランザクションをコミットしてください。_書き込み_が成功した場合はtrueを、そうでなければfalseを返します。例外をログに記録しようとはしないでください。
Database db = new Database();
Orm orm = new Orm(db);
orm.WriteSafely("good write");
// => true
orm.WriteSafely("bad write");
// => false
orm.WriteSafely("bad commit");
// => false