拡張メソッドが突然エラーに

タグの編集
投稿者 しまも  (社会人) 投稿日時 2023/1/12 12:02:27
はじめまして、VBを勉強し始めて1年弱の初心者(素人、趣味)です。
自前の外国語の単語帳ソフトを作りたくて、このサイトで勉強しながら少しずつ作ってきました。
ある程度できてきたのですが、突然、今まで使えていた拡張メソッドがエラーになってしまいました。

エラーになったのはDataTableの拡張メソッド(MaxNumberとMinimumLackedNumber)です。
お恥ずかしいですがコードを示します。

(呼び出し側) ★のところが赤波線のエラーに。
------------------------------------------------------------------------------------------
Public Class DBBookOperation

(略)

    '新しい本の登録。
    Public Sub AddBook(newBookName As String)

        Dim dtBook = BookDataTable()

        '最小欠番を探し、新しい本のコードに充てる。
        Dim bookCode As Integer = dtBook.MinimumLackedNumber("bookCode") ★
        'それが最大値以上を超えていたら、登録不可。
        If bookCode > CODE_MAX Then
            MsgBox("Database Full. No more book can be registered.")
            Return
        End If

        'showOrder(表示順)の最大値を調べる。新しい本のshowOrderはそれに1を加える。
        Dim showOrder As Integer = dtBook.MaxNumber("showOrder") + 1 ★

        Dim cvBookCode = New DBColumnValuePair(MyBase.Language, "tblBook", "bookCode", bookCode)
        Dim cvBookName = New DBColumnValuePair(MyBase.Language, "tblBook", "bookName", newBookName)
        Dim cvShowOrder = New DBColumnValuePair(MyBase.Language, "tblBook", "showOrder", showOrder)

        SqlInsert("tblBook", {cvBookCode, cvBookName, cvShowOrder})
        Renumber()

    End Sub

(略)
End Class

(拡張メソッド定義)
------------------------------------------------------------------------------------------
Public Module DatatableExtensions
    ''' <summary>
    '''     ''' DataTableの特定列の最大値を返す。
    ''' </summary>
    ''' <param name="dt"></param>
    ''' <param name="column"></param>
    ''' <returns></returns>
    <System.Runtime.CompilerServices.Extension>
    Public Function MaxNumber(dt As DataTable, column As String) As Integer

        If dt.Rows.Count > 0 Then
            Return Aggregate r In dt Into Max(CInt(r.Item(column)))
        Else
            Return 0
        End If
    End Function

    ''' <summary>
    ''' DataTableの特定列の最小欠番を返す。
    ''' </summary>
    ''' <param name="dt"></param>
    ''' <param name="column"></param>
    ''' <returns></returns>
    <System.Runtime.CompilerServices.Extension>
    Public Function MinimumLackedNumber(dt As DataTable, column As String) As Integer
        Dim max = MaxNumber(dt, column)
        Dim numbers = From r As DataRow In dt.Rows
                      Let n = r.Item(column)
                      Order By n
                      Select n
        Dim v As Integer = 1
        For i = 0 To max + 1
            If numbers(i) <> i Then
                v = i
                Exit For
            End If
        Next
        Return v
    End Function

(略)
End Module

出たのは次のようなエラーです。
------------------------------------------------------------------------------------------
BC30521
これらの引数に最も固有な、アクセス可能な'MaxNumber'がないため、オーバーロードの解決に失敗しました:
'DataTableExtensions'で定義された拡張メソッド'Public Function MaxNumber(column As String) As Integer:最も固有ではありません。
'DataTableExtensions'で定義された拡張メソッド'Public Function MaxNumber(column As String) As Integer:最も固有ではありません。

BC30521
これらの引数に最も固有な、アクセス可能な'MinimumLackedNumber'がないため、オーバーロードの解決に失敗しました:
'DataTableExtensions'で定義された拡張メソッド'Public Function MinimumLackedNumber(column As String) As Integer:最も固有ではありません。
'DataTableExtensions'で定義された拡張メソッド'Public Function MinimumLackedNumber(column As String) As Integer:最も固有ではありません。


------------------------------------------------------------------------------------------

さらに、これらメソッドがインテリセンスで次のように表示されます。

<拡張> Function MaxNum(column As String) As Integer (+1オーバーロード)
<拡張> Function MinimumLackedNumber(column As String) As Integer (+1オーバーロード)

どうやら、同じメソッドが二つあるかのように認識されている(?)ようなのですが……
しかし、オーバーロードは作っていませんし、隅々まで確認しましたが同じものをコピーしたりもしていません。
また、上記の部分は直近ではまったくいじっていません。
何より今まで動かせていたのが急に動かなくなり途方に暮れています。

原因や対処について教えていただけたら幸いです。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/12 13:51:30
まず試して頂きたいのは、ソリューションの「クリーン」を行ってから Visual Stdio を一度終了させてみて、
改めて起動しなおしてからリビルドしなおして、それで改善されるかどうかです。


クリーンしてみても駄目ならば、拡張メソッドとして呼び出している
 Dim bookCode As Integer = dtBook.MinimumLackedNumber("bookCode")
というコードを、拡張メソッドとしてではなく通常の
 Dim bookCode As Integer = Global.ルート名前空間名.DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode")
の呼び出しに替えてみて、エラーが解消されるかどうかを確認してみてください。


また、Module 内の
Function MinimumLackedNumber を
Function MinimumLackedNumber2 と置き換えた場合はどうでしょうか。

それによって、DBBookOperation.AddBook 内の
 Dim bookCode As Integer = dtBook.MinimumLackedNumber("bookCode") '★
という問題個所が、どのような変化を見せるか知りたいです。
 (1) 呼び出せるようになる
 (2) エラー BC30456 になる ('MinimumLackedNumber' は 何某 のメンバーではありません。
 (3) エラー BC30521 のまま (これらの引数に最も固有な、アクセス可能な'MaxNumber'がないため、オーバーロードの解決に失敗しました)


> 何より今まで動かせていたのが急に動かなくなり途方に暮れています。
エラーになっているのは、その 2 箇所だけなのでしょうか。
最近、プロジェクトのプロパティで「ルート名前空間」を変更したり、意図的に Namespace を拵えてみたとか、
あるいは Module に Partial キーワードをつけてみた…といった記憶も無いのですよね?

本当の意味で重複登録であったのなら、
BC30269『'Public Function MaxNumber(dt As DataTable, column As String) As Integer' には同じ署名で複数の定義が存在します。 』
が発生すると思いまし、重複ではなく競合だった場合には、
BC30179 『module 'DatatableExtensions' と module 'DatatableExtensions' が namespace '何某' で競合しています。』
が発生すると思うのですが、現状は、いずれも発生していない状況なのですよね…?

上記のいずれかが生じている場合、DBBookOperation.AddBook(String) のみならず、
DatatableExtensions 側におきましても、Public Function MinimumLackedNumber 内の
「Dim max = MaxNumber(dt, column)」の行で、オーバーロードを解決できないずに
BC30521 となるはずで、事象を再現するには至っていません。
投稿者 しまも  (社会人) 投稿日時 2023/1/13 10:26:08
早速にありがとうございます。

>ソリューションの「クリーン」を行ってから Visual Stdio を一度終了させてみて、
>改めて起動しなおしてからリビルドしなおして、それで改善されるかどうか

⇒クリーンとリビルド、やってみましたがダメでした。

>拡張メソッドとして呼び出している
> Dim bookCode As Integer = dtBook.MinimumLackedNumber("bookCode")
>というコードを、拡張メソッドとしてではなく通常の
> Dim bookCode As Integer = Global.ルート名前空間名.DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode")
>の呼び出しに替えてみて、エラーが解消されるかどうか

⇒◎これでなら動きました!

>Module 内の
>Function MinimumLackedNumber を
>Function MinimumLackedNumber2 と置き換えた場合


> (2) エラー BC30456 になる ('MinimumLackedNumber' は 何某 のメンバーではありません。
になりました。

>エラーになっているのは、その 2 箇所だけなのでしょうか。

⇒他にもう1つ、DataTableの拡張メソッドがあり、同様のエラーが出ていますが、省略しました。
なお、DataTable以外にStringやEnumerable(Of Integer)の拡張メソッドもいくつか作っていますが、
こちらは今のところエラー発生していません。

>最近、プロジェクトのプロパティで「ルート名前空間」を変更したり、意図的に Namespace を拵えてみたとか、
>あるいは Module に Partial キーワードをつけてみた…といった記憶も無いのですよね?
⇒どちらもしていません。いずれも私の知識を超える事柄で……。

ひとまず「拡張メソッドとしてではなく通常の呼び出し」にしたことで動くようにはなりました。
ありがとうございます!
しかし、狐につままれた気分です。できれば拡張メソッドとして使いたいですが……。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/13 11:07:59
今のところ、現象を再現させられるパターンを見つけ出せずにいます。

機微情報を取り除いて、必要最小限の実験コードにまで縮小した
サンプルコードを提示してもらえると追跡しやすいのですけれど、難しいですかね。

> ⇒◎これでなら動きました!
名前空間を明示すると動くのであれば、オーバーロードというよりは、
名前空間の異なる同名モジュールがあるパターンでしょうか…。
(あるいは拡張メソッドが別の DLL で提供されていた可能性もある?)

プロジェクトのプロパティの[参照設定]タブ内にある「インポートされた名前空間」あるいは
.vb ファイル先頭に書かれる Imports 宣言によって、.NET の標準ではない
自作コードの名前空間が追加されていたりはしますか?


さきほどの修正で
 = Global.ルート名前空間名.DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode")
とすることで呼べるようになったコードを
 = Global.ルート名前空間名.MinimumLackedNumber(dtBook, "bookCode") 'モジュール名省略
 = DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode") 'ルート名前空間省略
 = MinimumLackedNumber(dtBook, "bookCode") 'メソッド名のみ
に変えてみてください。この中に呼べなくなるパターンはありますか?


> 他にもう1つ、DataTableの拡張メソッドがあり、同様のエラーが出ていますが、省略しました。
そのエラーが、結果的に他のエラーを誘発する要因になっていた可能性も無いとは言えず。

Public Module DatatableExtensions を見てみると、
Public Function MinimumLackedNumber において
 Dim max = MaxNumber(dt, column)
という呼び出しが行われていますが、これを
 Dim max = dt.MaxNumber(column)
の拡張メソッド構文で呼び出すことはできるのでしょうか。
それとも、拡張メソッド構文だと、ここも BC30521 になってしまいますか?
投稿者 しまも  (社会人) 投稿日時 2023/1/14 15:09:37
>機微情報を取り除いて、必要最小限の実験コードにまで縮小した
>サンプルコードを提示してもらえると追跡しやすいのですけれど、難しいですかね。

⇒ありがとうございます。もし見ていただけるならとてもありがたいです。
 ただ、要領がわからなくて……どのようにすればよいでしょうか?
 機微情報にあたるようなものは特にありません。

>プロジェクトのプロパティの[参照設定]タブ内にある「インポートされた名前空間」あるいは
>.vb ファイル先頭に書かれる Imports 宣言によって、.NET の標準ではない
>自作コードの名前空間が追加されていたりはしますか?

上で示したDBBookOperationやDataTableExtensionsとは別のファイルの先頭に、
Imports VocabularyBuilder.Html
と書いているのはあります。「VocabularyBuilder」はソリューション名・プロジェクト名、「Html」は自作クラスです。

>さきほどの修正で
> = Global.ルート名前空間名.DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode")……①
>とすることで呼べるようになったコードを
> = Global.ルート名前空間名.MinimumLackedNumber(dtBook, "bookCode") 'モジュール名省略……②
> = DatatableExtensions.MinimumLackedNumber(dtBook, "bookCode") 'ルート名前空間省略……③
> = MinimumLackedNumber(dtBook, "bookCode") 'メソッド名のみ……④
>に変えてみてください。この中に呼べなくなるパターンはありますか?

すみません、私が
> ⇒◎これでなら動きました!
と言ったのは、①でなく③でした。
改めて①~④を試したところ、すべて呼ぶことができました。

>> 他にもう1つ、DataTableの拡張メソッドがあり、同様のエラーが出ていますが、省略しました。
>そのエラーが、結果的に他のエラーを誘発する要因になっていた可能性も無いとは言えず。

もう一つの拡張メソッドは次のものです。
前の2つと同様に、通常のメソッドとしての呼び出しに変えたら動きました。
    ''' <summary>
    ''' 新旧DataTableを比較し、行・列数や値が違っていたら真を、同じであれば偽を返す。
    ''' </summary>
    <System.Runtime.CompilerServices.Extension>
    Public Function HasValueChanged(dt1 As DataTable, dt2 As DataTable) As Boolean

        Dim rowCount1 As Integer = dt1.Rows.Count
        Dim clmCount1 As Integer = dt1.Columns.Count
        Dim rowCount2 As Integer = dt2.Rows.Count
        If rowCount1 = 0 AndAlso rowCount2 = 0 Then
            Return False
        ElseIf rowCount1 <> dt2.Rows.Count Then
            Return True
        End If

        For rowIndex = 0 To rowCount1 - 1
            For clmIndex = 0 To clmCount1 - 1
                Dim value1 = dt1.Rows(rowIndex).Item(clmIndex).ToString
                Dim value2 = dt2.Rows(rowIndex).Item(clmIndex).ToString
                If value1 <> value2 Then
                    Return True
                End If
            Next
        Next
        Return False
    End Function

>Public Function MinimumLackedNumber において
> Dim max = MaxNumber(dt, column)
>という呼び出しが行われていますが、これを
> Dim max = dt.MaxNumber(column)
>の拡張メソッド構文で呼び出すことはできるのでしょうか。

⇒こちらは拡張メソッド構文で呼び出すことができました。

あともう一つ、フォームデザイナーを開こうとすると、次のようなエラー(?)画面になり、デザイナー画面が開けなくなっていることに気づきました。
実行はできるのですが。フォームはいくつかありますが、一部を除きほとんどのフォームでこのようになります。
そういえば、拡張メソッドのエラーが出るようになったのは、フォームがこのようになってからのような気がします。
(ここに書かれていることは私にはちんぷんかんぷんです……。)

--------------------------------------------------------------------------------
×データが失われる可能性を防ぐため、デザイナーの読み込み前に以下のエラーを解決する必要があります。 
  ×1 個のエラー     無視して続行する     このページが表示されている理由   
×エラー HRESULT E_FAIL が COM コンポーネントの呼び出しから返されました。

 このエラーのインスタンス (1)  
 
1。   コール スタックの表示  
 
場所 System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
場所 Microsoft.VisualStudio.LanguageServices.Implementation.Utilities.Exceptions.ThrowEFail()
場所 Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.CodeTypeRef.LookupTypeSymbol()
 …(以下続く。投稿文字数制限のためとりあえず省略します)…


このエラーのヘルプ  
MSDN ヘルプ   

このエラーに関するフォーラムの投稿 
MSDN フォーラムでこのエラーに関連する投稿を検索   
--------------------------------------------------------------------------------
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/15 14:06:43
> もし見ていただけるならとてもありがたいです。
フォルダーごと zip 圧縮して、アップロードできますか? OneDrive の共有機能でも構いません。
https://fanblogs.jp/kzgadgets/archive/458/0

> Imports VocabularyBuilder.Html
> と書いているのはあります。
> 「VocabularyBuilder」はソリューション名・プロジェクト名、「Html」は自作クラスです。
それはプロジェクト名ではなく「ルート名前空間」ですね。

プロジェクトのプロパティの[アプリケーション]タブを見ると、
『アセンブリ名』(exe/dll ファイルの名前になるもの)と
『ルート名前空間』(Imports される際に指定されるもの)の
初期値がプロジェクト名と同じになっていますが、それぞれ別物です。


> あともう一つ、フォームデザイナーを開こうとすると、次のようなエラー(?)画面になり、デザイナー画面が開けなくなっていることに気づきました。
参照設定に、誤って「自分自身」が含まれている…ということは無いでしょうか。
投稿者 しまも  (社会人) 投稿日時 2023/1/18 19:16:42
ありがとうございます。
One Driveにコピーしてみましたが、こういう仕方でよろしいでしょうか。
https://1drv.ms/u/s!AhdkuT50ikIxeiz5KhKRskITe-Y?e=aUCBVk

>それはプロジェクト名ではなく「ルート名前空間」ですね。
概念の理解があやふやだったようです。ご指摘ありがとうございます。勉強します。

>参照設定に、誤って「自分自身」が含まれている…ということは無いでしょうか。
すみません、参照設定に「自分自身」が含まれているということの意味や
それをどう確かめるかなど、まるでちんぷんかんぷんで……。
VB初級編(改訂版)第7回あたりを勉強すればよいでしょうか……?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/18 23:41:25
ソースの御提示、ありがとうございます。
(警告が幾つか出ているようなので、問題が片付いたらそっちも添削したい…)

で、問題点は予想通りでした。

ソリューション エクスプローラーを見てみると、
[VocabularyBuilder] プロジェクトの下に
 [My Project]
 [参照]
 [各種フォルダー群]
 App.config
 Form1.vb
などのように並んでいるかと思いますが、この [参照]を開くと、
標準の .NET Framework が持つアセンブリにに加えて、
何故か [VocabularyBuilder]という、自身と同じ名前が見えると思います。

そして新規プロジェクトの場合、[参照]の下に自分自身は居ないはずです。

[参照]内の[VocabularyBuilder]を選択してみると、プロパティ ペインの「パス」欄に
~~\VocabularyBuilder\VocabularyBuilder\obj\Debug\VocabularyBuilder.exe
という記述が見えるかとおもいます。明らかにこれは、自分自身ですよね。


プログラムをコンパイルした場合、その最終成果物は、\bin の下に生成されるのですが、
そのために必要な中間ファイルは \obj フォルダーに作られます。
その、「以前作った中間生成物」である VocabularyBuilder.exe を、
なぜか自分自身が参照してしまっている状態です。

当然のことながら、このファイルの中には最終成果物と同一の型情報が含まれているため
今回の競合や、フォームデザイナの異常が生じてしまっている…という状況です。


以下の手順で回復できると思います。

(1) フォームデザイナやコードウィンドウなどが開かれていれば、それらをすべて閉じます。

(2) [参照]の一覧から右クリックして、自 exe への不要な参照を[削除]します。
 キーボードから [Delete] しても構いません。

(3) プロジェクトが参照しているファイル情報から [VocabularyBuilder] が取り除かれたので、
 改めて、[ビルド]メニューから[クリーン]して、中間ファイルと以前の生成物を削除します。


あとはもう一度ビルドしなおすことで、今回の事象が回復すると思います。
試してみてください。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/19 03:47:56
初心者を自負しておられますが、コードを見る限りでは
そうとは思えないほど良く書かれたコードですね。
(データベースが無いので実行はできませんが…)


まだ作成途中なのだとは思いますが、せっかくソースをを
見せていただいたので、ついでにコードレビューを。

=====
(1) フォーカス順について
-----
最初に tempMainMenu が起動しましたが、キーボードから[Tab]キーによるフォーカス移動を
行ったところ、選択される順序がが出鱈目でした。
また、GroupBox 内の RadioButton も、矢印キーでの移動順に違和感があります。

こうしたフォーカス移動順は、フォームのデザイン画面を開いているときに
[表示]-[タブ オーダー]を選択することで、簡単に調整できます。
tempMainMenu のタブオーダーを調整してみてください。
(LanguageSelecter や他のフォームも同様です)

なお、タブ オーダーについては、
[VB 初級講座] - [第19回 コントロールの順序]
の第5節で紹介されています。
http://rucio.o.oo7.jp/main/dotnet/shokyu/standard19.htm


=====
(2) フォームサイズについて
-----
タイトルバーをダブルクリックしたり、右上の最大化ボタンを押すことで
デスクトップいっぱいに広がりますよね。あるいはフォームを起動した後、
フォームの淵をドラッグしてサイズを大きくあるいは小さくできます。

しかし、各種ボタンなどは、そうしたサイズ変更に追従していません。

リサイズに対応する場合、その上のコントロールの配置も調整すべきです。
逆に固定配置とするのであれば、フォームをリサイズできないようにすべきです。

リサイズの可否については、FormBorderStyle プロパティで決定できます。
また、最大化ボタンの有無は MaximizeBox プロパティで決定します。
(最小化ボタンなら MinimizeBox プロパティ)
あるいはリサイズは可能にしたいけれど、最小値/最大値を制限するなら
MinimumSize/MaximumSize プロパティです。

リサイズに合わせてコントロールの位置や大きさを調整したいなら、
Anchor プロパティや Dock プロパティを使います。

この辺りは、初級講座の [第18回 コントロールの配置]に書かれています。
http://rucio.o.oo7.jp/main/dotnet/shokyu/standard18.htm
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/19 03:50:31
=====
(3) 型の不整合について
-----
コード中に、いわゆる「暗黙の型変換」が散見されます。

DatabaseOperation クラス - Function SqlSelect
 現行版:Dim rd = command.ExecuteScalar
 修正案:Dim rd = DirectCast(command.ExecuteScalar(), T)


DBSectionOperation クラス
 現行版:Private Property BookCode
 変更案:Private Property BookCode As Integer

 現行版:Friend Sub AddPluralSections(newSectionName, newNote, dtSection)
 変更案:Friend Sub AddPluralSections(newSectionName As String, newNote As String, dtSection As DataTable)


ExampleToHtml クラス
 現行版:Protected Overloads Function SetBaseText(sourceDt As DataTable, language As Language)
 変更案:Protected Overloads Function SetBaseText(sourceDt As DataTable, language As Language) As String


DBSectionOperation クラス - Function WordsInTheSection
 現行版:Return (From r As DataRow In dt.Rows Select CStr(r.Item("entry"))).ToList
 変更案1:Return (From r In dt Select CStr(r.Item("entry"))).ToList()
 変更案2:Return (From r In dt.AsEnumerable() Select CStr(r.Item("entry"))).ToList()
 変更案3:Return (From r In dt.Rows.OfType(Of DataRow)() Select CStr(r.Item("entry"))).ToList()
→元のコードだと、Object 型から DataRow 型への暗黙のボックス化解除が発生してしまいます。
 変更案の方法であれば最初から DataRow 型のまま取り出せるので、r As DataRow と書く必要がありません。


DefinitionToHtml クラス - Property ModifyingWays
 現行版:New HowToModify({italic}, ("⇒", ""))
 変更案1:New HowToModify({italic}, ("⇒"c, " "c))
 変更案2:New HowToModify({italic}, ("⇒"c, ControlChars.NullChar))
→ここはちょっと悩みどころ。
 まず、Char 型に対するリテラルは「"⇒"」ではなく「"⇒"c」表記です。
 そして Char 型は常に「1文字」なので、「0文字」である "" や、2文字以上の文字列は渡せません。
 もしも 0 文字や 2 文字などにすることがあり得るのであれば、
 As Char ではなく As String で受ける必要があるでしょう。
 ですから実際には、上記訂正案のように呼び出し側を Char 表記に揃えるのではなく、
 呼び出される側となる HowToModify クラスの方を
 「parenthes As (Char, Char)」→「parenthes As (String, String)」
 に揃えることを検討すべきかと思います。


DBColumn 構造体のコンストラクタ
 現行版:Me.Language = language
 修正案1:Me.Language = $"{language:D}"
 修正案2:Me.Language = language.ToString("D")
 修正案3:Me.Language = CStr(language) '非推奨
→右辺の値が Language.French の場合、現行版(暗黙の型変換時)では、左辺には "2" がセットされます。
 もしも左辺を "French" 表記にしたい場合は、D 書式の代わりに G 書式を使います。


DataGridViewCheck クラス - Function HasDgvError
 現行版:Where clms.Language = language AndAlso
 修正案1:Where System.Enum.Parse(GetType(Language), clms.Language) = language AndAlso
 修正案2:Where CInt(clms.Language) = language AndAlso
 修正案3:Where clms.Language = language.ToString("D") AndAlso
 修正案4:Where clms.Language = $"{language:D}" AndAlso
→現行版は、比較式の左辺が String、右辺が列挙型で不一致であるため、
 「Where CDbl(clms.Language) = CDbl(language) AndAlso」相当に暗黙変換されています。
→上記はいずれも、clms.Language に "2" などがセットされていることを前提としております。
 ただし修正案1 だけは、"2" の場合と "French" の場合の両方に対応することができます。


DataGridViewCheck クラス - Function CellErrorResult
 現行版:Dim isNull = Function(value) value Is Nothing OrElse value.ToString = ""
 修正案1:Dim isNull = Function(value As Object) value Is Nothing OrElse value.ToString() = ""
 修正案2:Dim isNull As Func(Of Object, Boolean) = Function(value) value Is Nothing OrElse value.ToString() = ""
 修正案3:Dim isNull = Function(value As Object) String.IsNullOrEmpty(value?.ToString())
→現行版だと value の型が定まりません(Object 扱い)ので、As 句で明示しておくか、もしくは型推論が可能な状態にします。


他にも、多数の「暗黙の型変換」に頼ったコードが見受けられましたので、一時的にでも
「Option Strict On」にしてみて、不自然な箇所を炙り出した方が良いでしょう。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/19 09:40:39
=====
(4) コンパイル時の各種警告について
-----
警告 BC42105: 関数 'SelectLanguageChrSet' は値を返さないコード パスがあります。結果が使用されるときに、null 参照の例外が実行時に発生する可能性があります。
→列挙型には、CType(100, Language) などどして、未知の値をセットすることもできるため、
 コンパイラ的には、これでは条件分岐が不十分と判断されます。
 万一、未知の値が渡されてしまった場合の振る舞いも定義しておきましょう。
 (案1) 何らかの例外を投げる
  Throw New ArgumentOutOfRangeException(NameOf(language), language, "不明な Language です")
 (案2) 不明な言語は、既知の言語のいずれかと同一視させる(たとえばFrench扱い)
 (案3) Return Nothing とする
 (案4) Return New Dictionary(Of Char, Char)()


警告 BC42104: 変数 'seqStr' は、値が割り当てられる前に使用されています。Null 参照の例外が実行時に発生する可能性があります。
警告 BC42104: 変数 'word' は、値が割り当てられる前に使用されています。Null 参照の例外が実行時に発生する可能性があります。
警告 BC42104: 変数 'lst' は、値が割り当てられる前に使用されています。Null 参照の例外が実行時に発生する可能性があります。
→seqStr は いずれの If 条件にも一致しなかった場合、
 word と lst は、いずれの Case 条件にも一致しなかった場合が考慮されていません。
 Else 条件を追加するか、あるいは変数宣言時に初期値をセットするようにしましょう。
 もしも初期値は設定したくないが、状況的に絶対に Else 句に来るはずもない、という場合は、
 Else 句にて例外を Throw する実装にしておくという手もあります。
 (たとえば InvalidOperationException、NotImplementedException、ArgumentException など)
※sequalNumStr デリゲートという名前もちょっとよく分からず。sequal とは何を意味しますか?


警告 BC42353: 関数 'HasDgvError' には値を返さないコード パスがあります。'Return' ステートメントが不足していないかどうかを確認してください。
→戻り値が As Boolean なのに、Return True が For ループの中にしかありません。
 列数が 0 だった場合、ループ内の Return が呼ばれないので注意されています。
 エラーではなく警告なのは、Boolean 型の既定値は False であるためですが、
 このケースでは、Next と End Function の間に「Return False」を設けるべきでしょう。


警告 BC42105: 関数 'GetRegularPlural' は値を返さないコード パスがあります。結果が使用されるときに、null 参照の例外が実行時に発生する可能性があります。
→ Function の中身が空です。オーバーライド先で困ってしまうので、
 戻り値をきちんと返すようにしましょう。あるいは MustInherit クラスで
 振る舞いを定める予定がない場合は、"Overridable" ではなく "MustOverride" を用います。
 動作を実装したくないという場合は、"Throw New NotImplementedException()" を記述しておくのも良いでしょう。


警告 BC42025: 共有メンバー、定数メンバー、列挙型メンバー、または入れ子にされた型にインスタンス経由でアクセスしています。正規の式は評価されません。
→ これは多数出ていますが、たとえば PopUpBase クラスを見てみます。この場合、
 「ElseIf e.KeyData = Keys.KeyCode.Escape Then」というコードが不自然です。
 左辺の e.KeyData の戻り値は System.Windows.Forms.Keys 列挙型を示していますので、
 右辺は System.Windows.Forms.Keys.Escape もしくは単に Keys.Escape とします。
 この警告を別の例で置き換えてみると、たとえば
  Dim w = DayOfWeek.Tuesday
 と書くべき箇所で
  Dim w = DayOfWeek.Sunday.Monday.Tuesday
 と書いていた場合に発生するものと同じである…と書けば、その不自然さが伝わるでしょうか。

 また、Keys.KeyCode という値は e.KeyData に対するビットマスクとして
 用いられるものであり、それ単体で比較されるような値でありません。
 Dim a As Keys = e.KeyData And Keys.KeyCode 'この a は e.KeyCode と等しい
 Dim b As Keys = e.KeyData And Keys.Modifiers 'この b は e.Modifiers と等しい
 Dim m1 As Boolean = e.Modifiers.HasFlag(Keys.Alt) 'この m1 は e.Alt と等しい
 Dim m2 As Boolean = e.Modifiers.HasFlag(Keys.Control) 'この m2 は e.Control と等しい
 Dim m3 As Boolean = e.Modifiers.HasFlag(Keys.ShiftKey) 'この m3 は e.Shift と等しい

 その直前にある
 「If e.KeyCode = Keys.KeyCode.Alt And e.KeyData = Keys.KeyCode.Enter Then」
 も同様の誤りを含んでいますが、こちらはそれに加えて、
 e.KeyData と e.KeyCode の混在利用という点も不自然です。
 (KeyData と KeyCode の違いは分かりますか?)
 それに論理演算ならば AndAlso/OrElse を使うべきです。
 ビット演算なら And/Or でも構いませんが、上記は論理演算のはずですよね。
投稿者 しまも  (社会人) 投稿日時 2023/1/20 23:48:15
魔界の仮面弁士様
早速につたないコードを見ていただき、エラーの原因・解決策、さらにコードレビューまで……誠にありがとうございます!!
ちょうど仕事などが立て込んできてしまったため、2~3週間ぐらい後になってしまうかもしれませんが、解決策を試し、指摘いただいた点の改善を取り組んでみます。先に取り急ぎお礼申し上げます!


※最後にご指摘いただいた「KeyData と KeyCode の違い」、図星でして、分かっていません。。。
ネットで調べてKeyDataというものとKeyCodeというものがあるらしい、というところまでは分かったのですが、それ以上よく分からなかったため、
  Debug.WriteLine("KeyData=" & e.KeyData & "; e.KeyCode=" & e.KeyCode)
のようなのを作り、実際にボタンを押してみて、出てきた数値をコードに入れてみた次第です。
望んだ結果が得られず、放置していたところでした。


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/21 09:27:09
> 2~3週間ぐらい後になってしまうかもしれませんが
了解です!
レビューを読んでいただけると分かったので、安心して指摘できます。(^^;


=====
(5) 他フォームの表示方法について
-----
フォームを表示する際には、
  Show メソッドによる、モードレスな呼び出し
  ShowDialog メソッドによる、モーダルでの呼び出し
がありますが、今回はすべて ShowDialog の方を使っておられるようですね。

で、その ShowDialog の呼び出し方がちょっと間違っています。

tempMainMenu フォームを例に挙げてみると、
Dim f = New FormWordMaster(Me.LanguageSelecter1.Value)
f.ShowDialog()
のように書かれていましたが、これらは正しくは
Using f As New FormWordMaster(Me.LanguageSelecter1.Value)
    f.ShowDialog(Me)
End Using
もしくは
Dim f As New FormWordMaster(Me.LanguageSelecter1.Value)
f.ShowDialog(Me)
f.Disoise()
のように、「Using ブロック」もしくは「Dispose() メソッド」を併用せねばならないのです。
※この点は、初級講座「第29回 2つ目のフォーム」でも触れられていません。

自作のダイアログのみならず、.NET Framework 標準の各種ダイアログについても同様です。
'下記の ColorDialog クラスは、標準で用意されている色選択ダイアログです 
Using dialog As New ColorDialog()
    dialog.FullOpen = True

    'ShowDialog は、戻り値で OK/Cancel などを返すことができます 
    If dialog.ShowDialog() = DialogResult.OK Then

        '閉じられた後でも、ダイアログのインスタンスはメモリ上に非表示で残り続けるので 
        'ダイアログが閉じられた後であったとしても、ユーザー選択した情報を 
        '後からプロパティで取り出すことができます 
        TextBox1.BackColor = dialog.Color

    End If

End Using  'ShowDialog 後もメモリ上に残っているので、明示的に破棄せねばなりません! 


ただし、このダイアログをまた後で再利用する場合(New しなおさない)には、
Using や Dispose での処分は不要です。
たとえば、上記の ColorDialog をフォームに貼って使う場合には、
 If Me.ColorDialog1.ShowDialog() = DialogResult.OK Then
  TextBox1.BackColor = Me.ColorDialog1.Color
 End If
と書くだけで済みます。この場合、ShowDialog は何度でも呼び出せます。


ShowDialog メソッドについて、公式ドキュメントで確認してみると、
フォームを表示し終わった後で「testDialog.Dispose()」と書いていますよね。
(言語モードが VB になっていない場合は、ページ右上に言語切替のドロップダウンリンクで切り替えます)
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.form.showdialog?view=netframework-3.5

上記には、以下の記述があります…が、機械翻訳がちょっとわかりにくいので、
単純引用ではなく、私なりに少し意訳しなおして記述してみます。

>>
>> フォームがモーダル ダイアログ ボックスとして表示された場合、
>> ユーザーが [ 閉じる ] ボタン (フォームの右上隅にある X ボタン) をクリックすると、
>> そのフォームは非表示となり、DialogResult プロパティには DialogResult.Cancel が返されます。
>> 
>> 非モーダル フォームとして呼び出された場合とは異なり、ユーザーがダイアログ ボックスの
>> 閉じるボタンをクリックしたり、DialogResult プロパティに値を設定したりしたとしても、
>> この時に .NET Frameworkによって Close メソッドが呼び出されることはありません。
>> 
>> その代わりに、フォームは単に非表示となります。そのため、ダイアログ フォームを
>> 再度表示しなおす際にも、新しいインスタンスを作成しなおす必要はありません。
>> ダイアログ ボックスとして表示されるフォームは閉じるのではなく非表示になるため、
>> あなたのアプリケーションでそのフォームが不要になった場合は、
>> 必ず Dispose メソッドを呼び出さねばなりません。(you must call the Dispose method)
>>

15年前は機械翻訳ではなく、ちゃんとした人力翻訳だったのですけれどね…。
一応、VS2008 当時の人力翻訳版も、ダウンロード版として存在してはいるのですが。
https://www.microsoft.com/ja-jp/download/details.aspx?id=20955


ちなみに、ColorDialog/FolderBrowserDialog/FontDialog/OpenFileDialog/SaveFileDialog といった
.NET Framework 標準のダイアログ コンポーネントについては、フォーム デザイナ上で
画面に貼って使う場合、それらのコンポーネントは、そのフォームが破棄される際に、
一緒に自動破棄される設計になっています。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/21 14:10:31
> ネットで調べてKeyDataというものとKeyCodeというものがあるらしい、というところまでは分かったのですが、それ以上よく分からなかったため、
KeyPress イベントの e.KeyChar は、さほど悩むところでもありませんが、
KeyDown / KeyUp イベントは、ちょっとわかりにくいですよね。
e.KeyCode / e.KeyData に加えて e.KeyValue というものまでありますし…。


まず、同時押しされる『修飾キー』ですが、これは e.Modifiers で得られます。

ここでいう修飾キーとは Ctrlキー、Shiftキー、Altキーの 3 種を
指しており、Windows キーはここに含まれません。
キーボードによっては、修飾キーが左右2か所に配置されることもありますが
複数のキーがあった場合、このイベントでは両者を区別できません。

もしも左右の修飾キー、たとえば LShiftKey と RShiftKey を区別したいような場合には、
ProcessKeyMessage メソッドをオーバーライドすることで調べられます。

修飾キーがどれも押されていなければ、e.Modifiers は Keys.None を返します。

3 種すべての修飾キーがすべて押されている場合には、
 If e.Modifiers = (Keys.Shift Or Keys.Control Or Keys.Alt) Then
のように、それぞれをビット Or 加算した値として捉えられます。
あるいは上記と同じ処理を、
 If e.Shift AndAlso e.Control AndAlso e.Alt Then
と書くこともできます。
And/Or/AndAlso/OrElse を混同しないように注意してください。


このように、KeyDown や KeyUp であれば e.Modifiers を使って修飾キーを捉えられますが、
KeyPress や MouseDown などには e.Modifiers がありません。しかし、同等機能が
Control クラスにある「ModifierKeys 共有プロパティ」からも得られますので、
 If e.Modifiers = (Keys.Shift Or Keys.Control Or Keys.Alt) Then
に相当する処理を
 If ModifierKeys = (Keys.Shift Or Keys.Control Or Keys.Alt) Then
と書くこともできます。


なお、文献によっては「Shift キーが押された状態」を
 If e.Modifiers.HasFlags(Keys.Shift) Then
 If (e.Modifiers And Keys.Shift) = Keys.Shift Then
 If (e.Modifiers And Keys.Shift) <> 0 Then
などと記していることもあるのですが、これらの記述法は注意が必要です。
上記はあくまでも Shift キーの状態しか判断していないため、
「Shift」だけでなく、「Ctrl+Shift」「Alt+Shift」「Ctrl+Alt+Shift」の
組み合わせにも反応することになってしまいます。



次は、一番大事な e.KeyCode 。これは、「修飾キー以外」のキーを指します。
また、e.KeyValue は CInt(e.KeyCode) と同義です。

e.KeyCode は物理的なキー位置を示しますので、
CapsLock が On の時の "A" も、CapsLock が Off の時の "a" も、
どちらも Keys.A という同じ情報が返されます。
("A" と "a" を区別する場合は、KeyPress イベントの e.KeyChar を使えます)

そしてこの e.KeyCode ですが……実は修飾キーが単体で押されている場合も
e.KeyCode が Keys.None になることはありません。次のような値になります。

Shift キーが押されている場合は、e.KeyCode = Keys.ShiftKey (Modifiers = Keys.Shift)
Ctrl キーが押されている場合は、e.KeyCode = Keys.ControlKey (Modifiers = Keys.Control)
Alt キーが押されている場合は、e.KeyCode = Keys.Menu (Modifiers = Keys.Alt)




最後は、混乱されていた e.KeyData ですが、
これは e.Modifiers と e.KeyCode のビット OR 演算したものです。
つまり「e.KeyData」と「e.Modifiers Or e.KeyCode」が同じ値になります。

e.KeyData の中から、e.Modifiers の成分だけを取り出す場合には、Keys.Modifiers 値をビット AND 演算します。
つまり「e.KeyData And Keys.Modifiers」と「e.Modifiers」が同じ値になります。

e.KeyData の中から、e.KeyCode の成分だけを取り出す場合には、Keys.KeyCode 値をビット AND 演算します。
つまり「e.KeyData And Keys.KeyCode」と「e.KeyCode」が同じ値になります。

これが、先の回答で『e.KeyData と e.KeyCode の混在利用は不自然』と述べた所以です。


つまり、元のコードでいうところの
> If e.KeyCode = Keys.KeyCode.Alt And e.KeyData = Keys.KeyCode.Enter Then 'Alt + Enter Key
のコードについては、実際には
 If e.Modifiers = Keys.Alt AndAlso e.KeyCode = Keys.Enter Then
あるいは
 If e.KeyData = (Keys.Alt Or Keys.Return) Then
もしくは
 If e.KeyCode = Keys.Enter AndAlso e.Alt AndAlso Not e.Control AndAlso Not e.Shift Then
などと記述します。(どれでも同じ結果が得られます)

結果だけ見れば、元のコードでも同じ結果を得られるのですが、
警告 BC42025 の件と合わせて見直しておかれた方が良いでしょう。
投稿者 しまも  (社会人) 投稿日時 2023/1/24 19:55:28
大変詳しく教えていただき、ありがとうございます。
遅くなりましたが、提示いただいた解決策を行なったところ、エラーは見事に直りました! フォームのデザイナーは表示でき、拡張メソッドも元通り使えるようになりました。
今、レビューで指摘いただいた点を順に直しています。大変勉強になります。ありがとうございました。