tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルCA2213の対処方法
記事No11746
投稿日: 2016/10/14(Fri) 10:12
投稿者オフディラン
開発環境:
Microsoft Windows 7 Professional
Visual Studio 2015
.Net Framework 4.5.2

2015のコード分析を実施した結果、警告「CA2213」がでます。
私なりに調べて、いろいろ試したのですが、解決できません。
どのようにすれば、いいのか教えて下さい。
よろしくお願いします。

-------------------------------------------------------------------------------
Imports System.Threading

Public Class Form1
    Private liTimer As Timer
    Private licenseDelegate As TimerCallback
    Public countZ As Integer

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        liTimer.Dispose()
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        countZ = 0
        licenseDelegate = New TimerCallback(AddressOf CheckTest)
        liTimer = New Timer(licenseDelegate, Nothing, 1000, 1000)
    End Sub

    Private Sub CheckTest()
        countZ += 1
        Console.WriteLine(countZ)
    End Sub

End Class
-------------------------------------------------------------------------------

上記のように、タイマーで、カウントアップするだけのコードですが、
以下の警告がでます。

警告    CA2213    
'Form1' は、IDisposable 型 'Timer' であるフィールド 'Form1.liTimer' を含んでいます。
このフィールドで Dispose または Close を呼び出すには、'Form1' の Dispose メソッドを変更してください。

'Form1' の Dispose メソッドを変更とは、どのように実装すればいいのか、
具体的なサンプルがみつからず、また、私の理解不足で分かりません。

よろしくお願いします。

[ツリー表示へ]
タイトルRe: CA2213の対処方法
記事No11747
投稿日: 2016/10/14(Fri) 15:23
投稿者魔界の仮面弁士
一つ気になったのは、カウントアップの変数。
マルチスレッドなプログラムである以上、
 Public countZ As Integer
 countZ += 1
というコードは聊か悪手だと思いますよ。

単にサンプルコードとして単純化したからだとは思いますので、
今回はこれ以上触れずにおきます。(別投稿 No.11748 として追記しておきました)


さて、本題。


> Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing

Dispose が必要なのは確かですが、フォームが閉じずに破棄された場合には、
FormClosing が発生しませんので、このタイミングでは不十分なのです。


これが System.Threading.Timer ではなく System.Windows.Forms.Timer であったなら
 liTimer = New Timer(Me.components)
あるいは
 Me.components.Add(liTimer)
とすることで、自動的に Dispose されるようにもできるのですけれど。


> 私なりに調べて、いろいろ試したのですが、解決できません。

コードエディタの中上のドロップダウンから「Form1」を選択してください
(「(Form1 イベント)」ではない事に注意)。そしてさらに続けて、
コードエディタの右上のドロップダウンから、淡色表示されている
「Dispose」を選択してみてさい。
Form1.vb から Form1.Designer.vb へ、コードウィンドウが切り替わるはずです。

この時記述されている内容は、概ね、下記の内容になっていると思います。
(フォームのデザイン状況によっては、多少異なっているかもしれません)

--------------------
'フォームがコンポーネントの一覧をクリーンアップするために dispose をオーバーライドします。
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    Try
        If disposing AndAlso components IsNot Nothing Then
            components.Dispose()
        End If
    Finally
        MyBase.Dispose(disposing)
    End Try
End Sub
--------------------

この Try ブロック内に、以下のコードを追加します。

        If disposing AndAlso liTimer IsNot Nothing Then
            liTimer.Dispose()
            liTimer = Nothing
        End If


これにより、警告 CA2213 が解消されるはずです。
現在の FormClosing イベントは不要になりますので、削除しておきましょう。


もし、Form1.Designer.vb にあるコードをあまり書き換えたくないのなら、
Sub Dispose(Boolean) メソッドが書かれた実装を、Form1.Designer.vb 上から
Form1.vb 上へと移動させてしまっても構いません。
移動に当たり、DebuggerNonUserCode 属性は付けたままでも外してしまっても OK です。

[ツリー表示へ]
タイトルRe^2: CA2213の対処方法
記事No11748
投稿日: 2016/10/14(Fri) 15:52
投稿者魔界の仮面弁士
一応補足。

>  Public countZ As Integer

この場合、Form1 以外からも countZ を読み書き出来ることになりますが、
マルチスレッド環境での同時更新はマズイです。


CheckTest 以外で使われる事がない変数なのであれば、
Public で公開する必要も無いので、Private な変数にしておきましょう。
あるいは Static なローカル変数にするという手もあります。


Form1 以外からの書き換えも必要なら、countZ フィールドを Public Property に書き換え、
同時アクセスに備えて、ReaderWriterLockSlim で保護するなどの対策が必要です。

    Private lockSlim As New ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion)
    Private _countZ As Integer = 0
    Public Property CountZ As Integer
        Get
            Dim c As Integer
            lockSlim.EnterReadLock()
            c = _countZ
            lockSlim.ExitReadLock()
            Return c
        End Get
        Set(value As Integer)
            lockSlim.EnterWriteLock()
            _countZ = value
            lockSlim.ExitWriteLock()
        End Set
    End Property


もし、更新は CheckTest のみで行い、他からは参照のみで良いのであれば、
countZ フィールドを Public ReadOnly Property にしてしまい、
編集はバッキング フィールド越しに行うという手もあります。

    Public ReadOnly Property CountZ As Integer
    Private Sub CheckTest()
        Console.WriteLine(Interlocked.Increment(_CountZ))
    End Sub


> licenseDelegate = New TimerCallback(AddressOf CheckTest)

TimerCallback のデリゲートは、本来は Sub() ではなく、Sub(state As Object) です。
折角 Code Analyzer を有効にするのであれば、ついでも Option Strict On も
付与しておくことをお奨めします。

[ツリー表示へ]
タイトルRe^3: CA2213の対処方法(解決)
記事No11749
投稿日: 2016/10/14(Fri) 17:20
投稿者オフディラン
魔界の仮面弁士 さま 回答ありがとうございます。

Form1.Designer.vb 内のコードは自動生成されるので、編集していはいけないと思い込んでいました。
教えて頂いたコードを追加することで、解決となりました。

countZについても、安易に書いたもので、
私の認識不足を指摘して頂いて、捕捉に記載された内容は、大変勉強になりました。

Option Strict On も今後、使用しようと思います。

まだまだ、本当に理解したとは言えませんが、今まで疑問だったことのヒントにもなりました。
私なりに調べて、勉強したいと思います。

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

[ツリー表示へ]