C#でSqlConnectionの後片付けをする時のusingの使い分け

# 背景

保守してたら、try-finallyでDBのコネクションの接続をクローズして管理しているコードを発見しました。
ぱっと見usingを使った方が良さげに思え書き換えたけど、例外が発生することに…。

※. usingはJavaでいうtry-with-resourcesという認識

# 結論

- コネクションの再利用する前提なのにusingで破棄していたことが原因
- コネクションの再利用しないならusing、再利用するならtry-finallyするなり自分で管理しよう！

# 調査

## テストコードで検証してみる

try-finallyとusingのそれぞれを使用した場合のサンプルコードで実験してみました。
長いので詳細は[こちら](https://ideone.com/CLJpqj)をご参照ください。

``` C#
public class SampleDao
{
    // 省略

    /// <summary>
    /// try-finallyでCloseする
    /// </summary>
    public void execute()
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = "SELECT GETDATE()";
        command.Connection = this.Connection;

        try { 
            command.Connection.Open();
            command.ExecuteReader();
        } finally { 
            command.Connection.Close();
        }
    }

    /// <summary>
    /// usingでCloseする
    /// </summary>
    public void executeWithUsing()
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = "SELECT GETDATE()";
        command.Connection = this.Connection;

        command.Connection.Open();

        using (command.Connection)
        { 
            command.ExecuteReader();
        }
    }
    // 省略
}
```

上記のサンプルを用いて以下のようなユニットテストで検証してみます。

``` C#
[TestClass]
public class UnitTest1
{
    // 省略

    [TestMethod]
    public void 複数利用で再利用する場合()
    {
        SampleDao dao1 = new SampleDao();

        // 初回
        try
        {
            dao1.execute();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreNotEqual("", dao1.Connection.ConnectionString);

        // 2回目
        try
        {
            dao1.execute();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreNotEqual("", dao1.Connection.ConnectionString);
    }

    // 省略

    [TestMethod]
    public void using_複数利用で再利用する場合()
    {
        SampleDao dao1 = new SampleDao();

        // 初回
        try
        {
            dao1.executeWithUsing();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreEqual("", dao1.Connection.ConnectionString);

        // 2回目
        try
        {
            dao1.executeWithUsing();
            Assert.Fail();
        }
        catch (Exception e)
        {
            Assert.AreEqual("", dao1.Connection.ConnectionString);
        }
    }
}
```


結果は以下のようになり、usingを使用したケースではコネクションの再利用はできないことになりました。

### usingを使用しない

| |初回|2回目|
|:----|:----|:----|
|単一利用で再利用しない場合|正常終了|N/A|
|複数利用で再利用しない場合|正常終了|正常終了|
|複数利用で再利用する場合|正常終了|正常終了|

### usingを使用

| |初回|2回目|
|:----|:----|:----|
|単一利用で再利用しない場合|正常終了|N/A|
|複数利用で再利用しない場合|正常終了|正常終了|
|複数利用で再利用する場合|正常終了|InvalidOperationException|

## そもそもusingってどういうもの？

Microsoftの[using ステートメント (C# リファレンス)](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-statement)を引用すると以下のように説明されていました。

>IDisposable オブジェクトの正しい使用を保証する簡易構文を提供します。 C# 8.0 以降は、using ステートメントによって IAsyncDisposable オブジェクトが適切に使用されるようになります。

上記の参考サイトから詳細として紹介されている[ステートメント > using ステートメント](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-specification/statements#the-using-statement)では

>ステートメントは、 using 1 つまたは複数のリソースを取得し、ステートメントを実行してから、リソースを破棄します。

と説明されていました。

私の言葉でまとめると、リソースは必ず**Dispose**メソッドによって破棄されるよ、ということが肝のようです。

※. ただし、Microsoft[SqlConnection.Close メソッド > 注釈](https://docs.microsoft.com/ja-jp/dotnet/api/system.data.sqlclient.sqlconnection.close?view=dotnet-plat-ext-6.0#--)では以下のように説明されておりちょっと紛らわしい

>Close と Dispose は機能的に同等です

## 今回のケースにおけるusingの使用有無での違い

こんな感じになります。

### usingを使用しない

1. コネクションをオープン
1. SQL実行
1. コネクションをクローズ
1. コネクションは破棄されていないので 1. に戻る

### usingを使用

1. コネクションをオープン
1. SQL実行
1. コネクションをクローズ
1. **Dispose**でコネクションが破棄
1. コネクションは破棄されているので 1. に戻ろうとすると例外が発生

# まとめ

今回は書き換えたusingによってコネクションが破棄されたことが、既存のコードとの違いであり、例外発生の原因でした。
そのため、実装によっては必ずしもusingがベストではない場合もあることがわかりました。
便利ですが仕様を理解しつつ注意して使っていきたいです！

# 参考

- [using ステートメント (C# リファレンス)](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-statement)
- [ステートメント > using ステートメント](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-specification/statements#the-using-statement)
- [SqlConnection.Close メソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.data.sqlclient.sqlconnection.close?view=dotnet-plat-ext-6.0)
- [using文で初期化したDbConnection、Closeを書くべき？書かなくていい？](https://qiita.com/momotaro98/items/c4fe0fff0c173e879f2d)
- [C# using() とは？意味と使い方は？try catch、close、dispose、例外処理、名前空間、自動リソース開放機能](https://urashita.com/archives/1825)