C# Language
ステートメントの使用
サーチ…
前書き
IDisposableオブジェクトの正しい使用を保証する便利な構文を提供します。
構文
- {使い捨て} {}を使って
- (IDisposable disposable = new MyDisposable()){}を使用して
備考
using
ステートメントのオブジェクトは、 IDisposable
インターフェイスを実装する必要があります。
using(var obj = new MyObject())
{
}
class MyObject : IDisposable
{
public void Dispose()
{
// Cleanup
}
}
IDisposable
実装のより完全な例は、 MSDNのドキュメントを参照してください 。
ステートメントの基本の使用
using
は、明示的なtry-finally
ブロックを必要とせずにリソースがクリーンアップされることを保証するための構文糖です。つまり、コードがはるかにクリーンになり、管理されていないリソースが漏れることはありません。
IDisposable
インターフェイス( FileStream
の基本クラスStream
が.NETで行う)を実装するIDisposable
に対しては、標準Dispose
クリーンアップパターンを使用します。
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
隠すことで構文using
単純化します:
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
はコードフローに関係なく呼び出されるため、 IDisposable
を実装するときにDispose
例外をスローしないようにすることをお勧めします。さもなければ、実際の例外は新しい例外によってオーバーライドされ、デバッグの悪夢となってしまいます。
ブロックを使用して戻る
using ( var disposable = new DisposableItem() )
{
return disposable.SomeProperty;
}
try..finally
の意味のため、 using
ブロックが変換され、 return
文は期待通りに機能します。戻り値は、 finally
ブロックが実行されて値がtry..finally
される前finally
評価されます。評価の順序は次のとおりです。
-
try
ボディを評価する - 戻り値を評価してキャッシュする
- finallyブロックを実行する
- キャッシュされた戻り値を返します。
ただし、変数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 Code Analysisルール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:あなたが処分しているリソースを返す
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
がすでにDBContext
されている場合にのみ実行される可能性があります。
つまり、式using
する前に評価されません。この問題を解決する方法の1つは、 using
を使用しますが、結果を列挙するメソッドを呼び出すことによって、式をすぐに評価することです。たとえば、 ToList()
、 ToArray()
などです。最新バージョンのEntity Frameworkを使用している場合は、 ToListAsync()
やToArrayAsync()
などのasync
カウンタを使用できます。
以下にその例を示します。
public IEnumerable<Person> GetPeople(int age)
{
using (var db = new DBContext())
{
return db.Persons.Where(p => p.Age == age).ToList();
}
}
しかし、 ToList()
やToArray()
呼び出すことで、表現が熱心に評価されることに注意することが重要です。つまり、指定した年齢の人が繰り返し処理を行わなくてもメモリにロードされます。
ステートメントの使用は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();
}
}
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
キーワードは、ステートメント内で定義されたリソースがステートメント自体のスコープ内にのみ存在することを保証します。ステートメント内で定義されたすべてのリソースは、 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などの多くのDbContext
ようなクラスの形式で基本データベースとやりとりするために使用される抽象クラスを公開します。これらのコンテキストは一般的にIDisposable
インターフェイスも実装しており、可能な限りステートメントをusing
これを活用する必要があります。
using(var context = new YourDbContext())
{
// Access your context and perform your query
var data = context.Widgets.ToList();
}
Dispose構文を使用してカスタムスコープを定義する
ユースケースによっては、 using
構文を使用し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
インスタンスは使用しないので、変数を代入しません。
このテクニックは、ASP.NET MVCのBeginForm
ヘルパーによって使用されます。
制約コンテキストでのコードの実行
特定の(制約)コンテキストで実行したいコード( ルーチン )がある場合は、依存関係注入を使用できます。
次の例は、開いている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()
句が必要ですusing()
try..finally
ブロックを除いて)唯一の方法であるため、クライアントコード( ExchangeSymmetricKey
)が使い捨てリソースを適切に処分することなく終了することを保証できます。 using()
節using()
ないと、ルーチンがコンテキストの制約を破ってそれらのリソースを破棄できないかどうかは決して分かりません。