InvalidOperationException について

初級者向けにおさらいします。

目次

この例外エラーの説明

Invalid Operation(無効な操作、間違った操作をすることで発生する)Exception です。命令の使い方だったり、VB.NET 言語自体のルールなどを知る必要があります。

事例とその対処方法

例外エラーは、想定外の扱われ方をすると発生して飛んで来ます。それは命令の使い方が間違っていたり、存在しないデータを扱おうとしていたり、アカウント権限を越えたアクセスをしようとしていたり(Windows 側やウェブ側の話だったり)、サービスが動いていないのに連携しようとしたり、製品上の仕様考慮モレだったり、そういう系です。

こうなっている前提のはずだから、こうやろうとしたのに、実際はここがこうなっているからダメじゃん!こういう場合の処理が無いじゃん!みたいなコードになっていませんか?

List(Of T)

ループ中にコレクションデータを編集するのはダメです。

Dim items = New List(Of Integer) From {1, 2, 3}

For Each item As Integer In items
    items.Remove(item)
Next
System.InvalidOperationException: 'コレクションが変更されました。列挙操作は実行されない可能性があります。'

コレクションデータを編集したい場合は、ループ用コレクションデータとは別に、編集用コレクションデータを用意して扱います。

Dim items = New List(Of Integer) From {1, 2, 3}
Dim workItems = items.ToList()

For Each item As Integer In items
    workItems.Insert(0, item)
    workItems.Remove(item)
    workItems.Add(item)
    workItems.Remove(item)
Next

非同期処理全般

画面を持つアプリケーション開発では、VB.NET では、別スレッドからのコントロールへの直接アクセスに対応しておらず、危険な操作として扱われます。これらの言葉や内容の意味は、マルチスレッドプログラミングや非同期処理を学ぶと分かるようになります。

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Me.Button1.Text = "Hello from Background!"
    End Sub

End Class
System.InvalidOperationException: '有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'Button1' がアクセスされました。'

このような場合は、UI スレッド上で実行するように Invoke メソッドを利用して操作します。かなり難しい話をしてますので、詳しい方に技術相談した方が良いと思います。

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

        ' 1
        ' Action ジェネリックデリゲート + 通常メソッド
        Dim method As Action(Of String) = AddressOf SetText
        If Me.InvokeRequired Then
            ' Invoke が必要な場合(つまり、現在別スレッド内にいるということ)
            Me.Invoke(method, New Object() {"Hello from Background!"})
        Else
            ' Invoke が不要な場合(つまり、現在 UI スレッド内にいるということ)
            Me.Button1.Text = "Hello from Background!"
        End If


        ' 2
        ' Action ジェネリックデリゲート + ラムダ式
        Dim method2 As Action(Of String) = Sub(s) Me.Button1.Text = s
        If Me.InvokeRequired Then
            ' Invoke が必要な場合(つまり、現在別スレッド内にいるということ)
            Me.Invoke(method2, New Object() {"Hello from Background!"})
        Else
            ' Invoke が不要な場合(つまり、現在 UI スレッド内にいるということ)
            'Me.Button1.Text = "Hello from Background!"
            method2("Hello from Background!")
        End If


        ' 3
        ' デリゲート(にセットしたラムダ式)を、UI スレッド上で動かす
        Me.Invoke(Sub() Me.Button1.Text = "Hello from Background!")

    End Sub

    Private Sub SetText(s As String)
        Me.Button1.Text = "Hello from Background!"
    End Sub

End Class