IDisposable オブジェクトの正しい使用を保証する便利な構文を提供します。
# Gotcha:破棄しているリソースを返す
以下は db
を破棄するため、悪い考えです。 変数を返す前に
public IDBContext GetDBContext()
{
using (var db = new DBContext())
{
return db;
}
}
これは、より微妙な間違いを引き起こす可能性もあります:
public IEnumerable<Person> GetPeople(int age)
{
using (var db = new DBContext())
{
return db.Persons.Where(p => p.Age == age);
}
}
これは問題ないように見えますが、問題は LINQ 式の評価が遅延しており、基になる DBContext
が後で実行される可能性があることです。
つまり、式は using
を離れる前に評価されません。 . using
を使用するこの問題の 1 つの可能な解決策 、結果を列挙するメソッドを呼び出すことにより、式がすぐに評価されるようにします。例えば ToList()
、 ToArray()
など。最新バージョンの Entity Framework を使用している場合は、 async
を使用できます ToListAsync()
のような対応 または ToArrayAsync()
.
以下に実際の例を示します:
public IEnumerable<Person> GetPeople(int age)
{
using (var db = new DBContext())
{
return db.Persons.Where(p => p.Age == age).ToList();
}
}
ただし、 ToList()
を呼び出すことによって、 または ToArray()
、式は熱心に評価されます。つまり、反復処理を行わなくても、指定された年齢を持つすべての人がメモリに読み込まれます。
# ステートメントの基本の使用
using
明示的な try-finally
を必要とせずにリソースがクリーンアップされることを保証できるシンタックス シュガーです。 ブロック。これは、コードがよりクリーンになり、管理されていないリソースがリークしないことを意味します。
標準 Dispose
IDisposable
を実装するオブジェクトのクリーンアップ パターン インターフェイス (FileStream
の基本クラス Stream
.NET で行います):
int Foo()
{
var fileName = "file.txt";
{
FileStream disposable = null;
try
{
disposable = File.Open(fileName, FileMode.Open);
return disposable.ReadByte();
}
finally
{
// finally blocks are always run
if (disposable != null) disposable.Dispose();
}
}
}
using
明示的な try-finally
を隠すことで構文を簡素化します :
int Foo()
{
var fileName = "file.txt";
using (var disposable = File.Open(fileName, FileMode.Open))
{
return disposable.ReadByte();
}
// disposable.Dispose is called even if we return earlier
}
finally
のように ブロックはエラーやリターンに関係なく常に実行されます using
常に Dispose()
を呼び出します 、エラーが発生した場合でも:
int Foo()
{
var fileName = "file.txt";
using (var disposable = File.Open(fileName, FileMode.Open))
{
throw new InvalidOperationException();
}
// disposable.Dispose is called even if we throw an exception earlier
}
注: Dispose
以降 コードフローに関係なく呼び出されることが保証されているため、 Dispose
を確認することをお勧めします IDisposable
を実装すると例外がスローされることはありません .そうしないと、実際の例外が新しい例外によってオーバーライドされ、デバッグの悪夢が発生します。
# ブロックの使用から戻る
using ( var disposable = new DisposableItem() )
{
return disposable.SomeProperty;
}
try..finally
のセマンティクスのため using
ブロック変換、return
ステートメントは期待どおりに機能します - 戻り値は finally
の前に評価されます ブロックが実行され、値が破棄されます。評価の順序は次のとおりです:
try
を評価する 体
ただし、変数 disposable
を返さない場合があります 無効な破棄された参照が含まれているため、関連する例を参照してください。
# 1 つのブロックで複数の using ステートメント
複数のネストされた using
を使用することが可能です 複数レベルのネストされた中括弧を追加しないステートメント。例:
using (var input = File.OpenRead("input.txt"))
{
using (var output = File.OpenWrite("output.txt"))
{
input.CopyTo(output);
} // output is disposed here
} // input is disposed here
別の方法は次のように書くことです:
using (var input = File.OpenRead("input.txt"))
using (var output = File.OpenWrite("output.txt"))
{
input.CopyTo(output);
} // output and then input are disposed here
これは最初の例とまったく同じです。
注: ネストされた using
ステートメントは、Microsoft コード分析ルール CS2002 をトリガーし (明確にするためにこの回答を参照)、警告を生成する可能性があります。リンクされた回答で説明されているように、 using
をネストすることは一般的に安全です
using
内の型の場合 ステートメントは同じタイプです。それらをカンマで区切り、タイプを 1 回だけ指定できます (これは一般的ではありません):
using (FileStream file = File.Open("MyFile.txt"), file2 = File.Open("MyFile2.txt"))
{
}
これは、タイプに共有階層がある場合にも使用できます:
using (Stream file = File.Open("MyFile.txt"), data = new MemoryStream())
{
}
var
キーワードはできません 上記の例で使用します。コンパイルエラーが発生します。宣言された変数が異なる階層の型を持つ場合、カンマ区切りの宣言でも機能しません。
# Gotcha:Dispose メソッドで例外がブロックの使用で他のエラーをマスクする
次のコード ブロックを検討してください。
try
{
using (var disposable = new MyDisposable())
{
throw new Exception("Couldn't perform operation.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
class MyDisposable : IDisposable
{
public void Dispose()
{
throw new Exception("Couldn't dispose successfully.");
}
}
コンソールに「操作を実行できませんでした」と表示されることを期待するかもしれませんが、実際には「正常に破棄できませんでした」と表示されます。最初の例外がスローされた後でも Dispose メソッドが呼び出されるためです。
オブジェクトの破棄を妨げ、デバッグを困難にしている実際のエラーを隠している可能性があるため、この微妙な点に注意する価値があります。
# Using ステートメントは null セーフです
IDisposable
をチェックする必要はありません null
のオブジェクト . using
例外をスローせず、Dispose()
呼び出されません:
DisposableObject TryOpenFile()
{
return null;
}
// disposable is null here, but this does not throw an exception
using (var disposable = TryOpenFile())
{
// this will throw a NullReferenceException because disposable is null
disposable.DoSomething();
if(disposable != null)
{
// here we are safe because disposable has been checked for null
disposable.DoSomething();
}
}
# Dispose Syntax を使用してカスタム スコープを定義する
ユースケースによっては、using
を使用できます カスタム スコープの定義に役立つ構文。たとえば、次のクラスを定義して、特定のカルチャでコードを実行できます。
public class CultureContext : IDisposable
{
private readonly CultureInfo originalCulture;
public CultureContext(string culture)
{
originalCulture = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
}
public void Dispose()
{
Thread.CurrentThread.CurrentCulture = originalCulture;
}
}
その後、このクラスを使用して、特定のカルチャで実行されるコード ブロックを定義できます。
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
using (new CultureContext("nl-NL"))
{
// Code in this block uses the "nl-NL" culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25-12-2016 00:00:00
}
using (new CultureContext("es-ES"))
{
// Code in this block uses the "es-ES" culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25/12/2016 0:00:00
}
// Reverted back to the original culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 12/25/2016 12:00:00 AM
注:CultureContext
を使用しないため 作成するインスタンスに変数を割り当てません。
このテクニックは BeginForm
で使用されています ASP.NET MVC のヘルパー。
# ステートメントとデータベース接続の使用
using
キーワードは、ステートメント内で定義されたリソースがステートメント自体のスコープ内にのみ存在することを保証します。ステートメント内で定義されたすべてのリソースは、IDisposable
を実装する必要があります
IDisposable
を実装する接続を扱う場合、これらは非常に重要です。 接続が適切に閉じられるだけでなく、 using
の後にそれらのリソースが解放されることを保証できるため、インターフェース ステートメントは範囲外です。
# 共通 IDisposable
データ クラス
以下の多くは、IDisposable
を実装するデータ関連のクラスです。 インターフェイスであり、using
の完璧な候補です ステートメント:
SqlConnection
,SqlCommand
,SqlDataReader
などOleDbConnection
,OleDbCommand
,OleDbDataReader
などMySqlConnection
、MySqlCommand
、MySqlDbDataReader
などDbContext
これらはすべて、C# を介してデータにアクセスするために一般的に使用され、データ中心のアプリケーションを構築する際によく見られます。同じ FooConnection
を実装する、言及されていない他の多くのクラス ,FooCommand
,FooDataReader
クラスは同じように動作することが期待できます。
# ADO.NET 接続の共通アクセス パターン
ADO.NET 接続を介してデータにアクセスするときに使用できる一般的なパターンは次のようになります:
// This scopes the connection (your specific class may vary)
using(var connection = new SqlConnection("{your-connection-string}")
{
// Build your query
var query = "SELECT * FROM YourTable WHERE Property = @property");
// Scope your command to execute
using(var command = new SqlCommand(query, connection))
{
// Open your connection
connection.Open();
// Add your parameters here if necessary
// Execute your query as a reader (again scoped with a using statement)
using(var reader = command.ExecuteReader())
{
// Iterate through your results here
}
}
}
または、単純な更新を実行するだけでリーダーを必要としない場合は、同じ基本概念が適用されます:
using(var connection = new SqlConnection("{your-connection-string}"))
{
var query = "UPDATE YourTable SET Property = Value WHERE Foo = @foo";
using(var command = new SqlCommand(query,connection))
{
connection.Open();
// Add parameters here
// Perform your update
command.ExecuteNonQuery();
}
}
# DataContext でステートメントを使用する
Entity Framework などの多くの ORM は、DbContext
のようなクラスの形式で、基になるデータベースと対話するために使用される抽象化クラスを公開します。 .これらのコンテキストは通常、IDisposable
を実装します インターフェイスも同様であり、using
を介してこれを利用する必要があります 可能な場合のステートメント:
using(var context = new YourDbContext())
{
// Access your context and perform your query
var data = context.Widgets.ToList();
}
# 制約コンテキストでのコードの実行
コードがある場合 (ルーチン ) 特定の (制約) コンテキストで実行したい場合は、依存性注入を使用できます。
次の例は、開いている SSL 接続で実行するという制約を示しています。この最初の部分はライブラリまたはフレームワークにあり、クライアント コードには公開しません。
public static class SSLContext
{
// define the delegate to inject
public delegate void TunnelRoutine(BinaryReader sslReader, BinaryWriter sslWriter);
// this allows the routine to be executed under SSL
public static void ClientTunnel(TcpClient tcpClient, TunnelRoutine routine)
{
using (SslStream sslStream = new SslStream(tcpClient.GetStream(), true, _validate))
{
sslStream.AuthenticateAsClient(HOSTNAME, null, SslProtocols.Tls, false);
if (!sslStream.IsAuthenticated)
{
throw new SecurityException("SSL tunnel not authenticated");
}
if (!sslStream.IsEncrypted)
{
throw new SecurityException("SSL tunnel not encrypted");
}
using (BinaryReader sslReader = new BinaryReader(sslStream))
using (BinaryWriter sslWriter = new BinaryWriter(sslStream))
{
routine(sslReader, sslWriter);
}
}
}
}
ここで、SSL の下で何かを実行したいが、すべての SSL の詳細を処理したくないクライアント コード。対称鍵の交換など、SSL トンネル内で必要なことは何でもできるようになりました:
public void ExchangeSymmetricKey(BinaryReader sslReader, BinaryWriter sslWriter)
{
byte[] bytes = new byte[8];
(new RNGCryptoServiceProvider()).GetNonZeroBytes(bytes);
sslWriter.Write(BitConverter.ToUInt64(bytes, 0));
}
このルーチンは次のように実行します:
SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);
これを行うには、using()
が必要です 句はそれが唯一の方法であるため (try..finally
を除く) ブロック) クライアントコード (ExchangeSymmetricKey
) を保証できます ) 使い捨て可能なリソースを適切に破棄せずに終了することはありません。 using()
なし 節では、ルーチンがコンテキストの制約を破ってそれらのリソースを破棄できるかどうかはわかりません。
# 構文
- 使用 (使い捨て) { }
- using (IDisposabledisposable =new MyDisposable()) { }
# コメント
using
のオブジェクト ステートメントは IDisposable
を実装する必要があります
using(var obj = new MyObject())
{
}
class MyObject : IDisposable
{
public void Dispose()
{
// Cleanup
}
}
IDisposable
のより完全な例 実装については、MSDN ドキュメントで確認できます。