サーチ…


前書き

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評価されます。評価の順序は次のとおりです。

  1. tryボディを評価する
  2. 戻り値を評価してキャッシュする
  3. finallyブロックを実行する
  4. キャッシュされた戻り値を返します。

ただし、変数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ステートメントの完全な候補となるデータ関連クラスです。

  • SqlConnectionSqlCommandSqlDataReaderなど
  • OleDbConnectionOleDbCommandOleDbDataReaderなど
  • MySqlConnectionMySqlCommandMySqlDbDataReaderなど
  • DbContext

これらはすべて、C#を介してデータにアクセスするために一般的に使用され、データ中心のアプリケーションを構築する際に一般に遭遇します。同じFooConnectionFooCommandFooDataReaderクラスを実装する、言及されていない他の多くのクラスは、同じように動作することが期待できます。

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()ないと、ルーチンがコンテキストの制約を破ってそれらのリソースを破棄できないかどうかは決して分かりません。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow