none
入れ子の using に対する CA2202 違反の解決策について RRS feed

  • 質問

  • Visual Studio 2013 Professional
    .Net Framework 4.5 以降で開発しています。

    次のように、Stream クラスと StreamWriter クラスの using を入れ子にした形にすると、
    コード分析の結果 CA2202 の警告が検出されてしまいます。

    using (var z = ZipFile.Open("FileName", ZipArchiveMode.Create))
    {
        var entry = z.CreateEntry("hoge");
        using (var stream = entry.Open())
        using (var writer = new StreamWriter(stream))
        {
            writer.Write("fuga");
        }
    }

    Microsoft の公式サイトを調べてみると try/finally を使えと書いてあるのですが、
    それでは using の意味がないし、
    そもそも try/finally を使っても同じく CA2202 が検出されてしまいます。

    https://msdn.microsoft.com/ja-jp/library/ms182334.aspx

    StreamWriter クラスを Dispose するときに、渡されている Stream クラスも Dispose するので、
    Stream クラスを二重に Dispose することになるそうです。
    そこで、次のようなコードにしたところ、CA2202 は検出されなくなりました。

    using (var z = ZipFile.Open("FileName", ZipArchiveMode.Create))
    {
        var entry = z.CreateEntry("hoge");
        using (var writer = new StreamWriter(entry.Open()))
        {
            writer.Write("fuga");
        }
    }

    これで問題解決かとも思ったのですが、
    仮に StreamWriter クラスのコンストラクタで例外が発生した場合、
    引き渡された Stream クラスは果たして Dispose されるのでしょうか。

    2015年11月11日 3:42

回答

  • こんにちは。

    最初はSuppressMessageで良いのではと思ったのですが、
    以下によるとあまり推奨されていないようです。
    https://msdn.microsoft.com/ja-jp/library/ms182334.aspx?f=255&MSPPError=-2147217396

    結局はTryCatchで括ることになると思うのですが、StreamWriterインスタンス生成後にnullセットで、私は警告回避できたみたいですが…。

    public void Do()
    {
        using (var z = ZipFile.Open("FileName", ZipArchiveMode.Create))
        {
            var entry = z.CreateEntry("hoge");
            Stream stream = null;
            try
            {
                stream = entry.Open();
                using (var writer = new StreamWriter(stream))
                {
                    stream = null;
                    writer.Write("fuga");
                }
            }
            finally
            {
                if (stream != null)
                    stream.Dispose();
            }
        }
    }
    

    • 回答としてマーク Yujiro15 2015年11月11日 6:22
    2015年11月11日 5:07
    モデレータ

すべての返信

  • されません。破棄はファイナライザまで遅れることになりますね。(といっても大元のZipFileがDisposeされるので問題にはならないような気がしますが)

    Streamの方をusingするようにして、StreamWriterは裸で使うことを勧めます。

    2015年11月11日 3:53
  • やはり破棄されないんですね。
    確かに今回の例では、一番外側の ZipFile が Dispose されれば問題ないかもしれないですが、
    CA2202 の件に関しては、このような形でなくても発生するので、
    対処法についてきちんと知っておく必要があると思っています。

    Streamの方をusingするようにして、StreamWriterは裸で使うことを勧めます。

    StreamWriter のほうを裸で使うというのはこういうことでしょうか。

    using (var z = ZipFile.Open("FileName", ZipArchiveMode.Create))
    {
        var entry = z.CreateEntry("hoge");
        using (var stream = entry.Open())
        {
            var writer = new StreamWriter(stream);
            writer.Write("fuga");
        }
    }

    この場合、StreamWriter のほうは Dispose されませんよね。
    ちなみにこのコードの内側で try/finally を使って StreamWriter を Dispose するようにしたら
    また CA2202 の警告が検出されてしまいました。
    2015年11月11日 4:21
  • 参考までに。

    StreamWriterコンストラクターには元となるストリームを閉じないleaveOpenフラグを持つオーバーロードが存在します。これを使えば警告内容には対処できます。しかしこれを試してみたものの、CA2202警告が出ることに変わりありませんでした。

    2015年11月11日 4:54
  • こんにちは。

    最初はSuppressMessageで良いのではと思ったのですが、
    以下によるとあまり推奨されていないようです。
    https://msdn.microsoft.com/ja-jp/library/ms182334.aspx?f=255&MSPPError=-2147217396

    結局はTryCatchで括ることになると思うのですが、StreamWriterインスタンス生成後にnullセットで、私は警告回避できたみたいですが…。

    public void Do()
    {
        using (var z = ZipFile.Open("FileName", ZipArchiveMode.Create))
        {
            var entry = z.CreateEntry("hoge");
            Stream stream = null;
            try
            {
                stream = entry.Open();
                using (var writer = new StreamWriter(stream))
                {
                    stream = null;
                    writer.Write("fuga");
                }
            }
            finally
            {
                if (stream != null)
                    stream.Dispose();
            }
        }
    }
    

    • 回答としてマーク Yujiro15 2015年11月11日 6:22
    2015年11月11日 5:07
    モデレータ
  • leaveOpen という引数は初めて知りました。
    確かに対処できそうですが、警告は相変わらず出ますね...。
    情報提供ありがとうございます。
    2015年11月11日 5:55
  • こんにちは。

    確かに using 直後に null をセットすると警告が消え、
    それ以外のところで null をセットすると警告が検出されますね。
    これは気付きませんでした。

    確かに、
    StreamWriter クラスのインスタンス生成の直後に
    真っ先に Stream クラスのインスタンス参照に null を入れてしまえば、
    その後どうやって using を抜けたとしても
    Stream クラスのほうを二重に Dispose することはないですね。
    コード分析はここまで見てくれている、ということに少し感動を覚えました。
    悪く言うと重箱の隅をつつき過ぎててちょっとうるさいですが...。

    警告が消えるのは嬉しいことですが、
    果たしてこのコードが一番綺麗なのかどうかは微妙、というのが個人的な意見です。
    ですが、上記でも挙げている Microsoft の公式ページでも
    同じように using 直後で null をセットしているようなので、
    結局のところ、これが妥協点なのかもしれません。

    ありがとうございました。

    2015年11月11日 6:22
  • 個人的にはSuppressMessageで十分と思います。

    警告をなくすためにコードで小細工をするのは本末転倒のように思いますし。

    意味のないコードは意味が分かりませんので、メンテナンスでおかしくなってしまう可能性がありますし、コード的に意味のないnull代入とかあったら、コードレビューとかで逆に指摘してしまいそうです。

    SuppressMessageが何らかの理由で使えないなら警告の出る行あたりにコメントで警告は無視してよろしいと書いといてもいいくらいだと思いますし。

    ※どうしても警告を消さなきゃならないならそうはいきませんが。

    2015年11月11日 7:04