WinForms のテーマ切り替え処理などでコントロールを総なめして色を変えていると、コード分析から CA1800「不要なキャストを避ける」が出てモヤっとすることがあります。本記事では、VB.NET/WinForms でコントロールの型変換(Cast Controls)を行う際にこの警告をきれいに解消する実装パターンを、具体的なコード例付きで詳しく解説します。
CA1800「不要なキャストを避ける」とは?
まずは、問題の警告そのものを整理しておきます。
CA1800: Avoid unnecessary casting(不要なキャストを避ける) は、Visual Studio のコード分析/Roslyn アナライザーが出す警告で、
- 同じ変数に対して複数回キャストしている
- キャスト結果を変数に保持せず、毎回条件式の中でキャストしている
といったコードに対して、「一度だけキャストして結果をキャッシュした方が安全で効率的だよ」と教えてくれるものです。
典型的には、次のようなコードで発生します。
For Each ctrl As Control In InputForm.Controls
If TypeOf ctrl Is Label Then
DirectCast(ctrl, Label).BackColor = Color.Red
End If
If TypeOf ctrl Is customButton Then
DirectCast(ctrl, customButton).BackColor = Color.Red
End If
Next
人間が読むと「Label と customButton は別物だからいいでしょ?」と言いたくなりますが、アナライザーは
ctrlという同じ変数に対して- 別々の場所で
DirectCastをしている
という事実だけを見て「複数回キャストしているので良くない」と判断してしまいます。
WinForms のコントロール走査で警告が出る典型パターン
質問で多いのが、フォームのコントロールを走査してテーマ色を適用するパターンです。
For Each ctrl As Control In InputForm.Controls
If TypeOf ctrl Is Label Then
Dim lbl As Label = DirectCast(ctrl, Label)
lbl.BackColor = Color.FromArgb(255, 1, 2, 3)
End If
If TypeOf ctrl Is customButton Then
Dim btn As customButton = DirectCast(ctrl, _ButtonBar) ' ← ここも問題になりがち
btn.BackColor = Color.FromArgb(255, 1, 2, 3)
End If
Next
このコードには、実は複数の問題が潜んでいます。
| 問題点 | 内容 | 影響 |
|---|---|---|
| 同一変数への複数キャスト | ctrl に対して Label と customButton の2種類のキャストを行っている。 | CA1800「不要なキャスト」が出る。 |
| 型判定とキャストの不一致 | TypeOf ctrl Is customButton なのに DirectCast(ctrl, _ButtonBar) など、条件とキャスト先の型が違う。 | コード分析が誤解しやすく、最悪の場合は実行時例外の原因になる。 |
| キャスト結果の使い捨て | キャスト結果を変数に保持せず、その場で使っている。 | 将来、処理が増えたときに同じキャストを何度も書きがち。 |
コード分析の警告を「無視する」のは簡単ですが、せっかくなので根本的にきれいな書き方にしておくと、メンテナンス性も上がります。
解決策1:TryCast で一度だけキャストして結果を使い回す(最もおすすめ)
まず、もっともシンプルかつ安全で、CA1800 も確実に解消できるパターンです。
For Each ctrl As Control In InputForm.Controls
Dim lbl As Label = TryCast(ctrl, Label)
If lbl IsNot Nothing Then
lbl.BackColor = Color.FromArgb(255, 1, 2, 3)
Continue For
End If
Dim btn As customButton = TryCast(ctrl, customButton)
If btn IsNot Nothing Then
btn.BackColor = Color.FromArgb(255, 1, 2, 3)
Continue For
End If
Next
TryCast は、キャストに失敗しても例外を投げず Nothing を返すのがポイントです。これにより、
- キャストは1回だけ
- 成功したら変数(
lbl/btn)に保持 IsNot Nothingで判定できるので、型チェック+キャストを1セットで完結
という構造になります。
TryCast パターンのメリット・デメリット
| 項目 | 内容 |
|---|---|
| 安全性 | キャスト失敗が Nothing になるだけなので例外が発生しない。 |
| CA1800 対策 | キャストは各型に対して 1回だけ。結果も変数に保持するため警告は出ない。 |
| 可読性 | lbl IsNot Nothing というパターンが分かりやすく、コピペもしやすい。 |
| パフォーマンス | キャストを 1回で済ませているので無駄が少ない。通常の業務アプリでは十分高速。 |
また、Continue For を入れているのは、同じコントロールに対して他の型チェックが走らないようにするためです。
- Label と customButton の両方を継承しているような特殊なケースがあったとしても、「最初にマッチした方だけ処理する」という仕様になります。
- 大半の WinForms アプリでは「一つのコントロールは一つの型」と考えてよいので、この挙動で問題になることはほぼありません。
型判定とキャスト先の型は必ず一致させる
特に注意しておきたいのが、
- 型判定に使う型
- キャスト先の型
が必ず一致していることです。例えば以下のようなコードは絶対に避けてください。
Dim btn As customButton = TryCast(ctrl, customButton)
If btn IsNot Nothing Then
Dim bar As _ButtonBar = DirectCast(ctrl, _ButtonBar) ' 条件とキャスト先が違う
' ...
End If
このように、「customButton であることを確認したのに _ButtonBar にキャストする」といったコードは、アナライザーにも人間にも理解されにくく、バグの温床になります。
| パターン | 例 | 評価 |
|---|---|---|
| ◎ 良い例 | Dim btn = TryCast(ctrl, customButton)→ If btn IsNot Nothing Then ... | 判定とキャスト先が 1:1 で対応。CA1800 も出ない。 |
| △ 紛らわしい例 | If TypeOf ctrl Is customButton ThenDim btn = DirectCast(ctrl, customButton) | 動くが、TryCast でまとめた方が安全。CA1800 が出る場合もある。 |
| × 悪い例 | If TypeOf ctrl Is customButton ThenDim bar = DirectCast(ctrl, _ButtonBar) | 論理的に不整合。実行時例外の可能性もあり、避けるべき。 |
解決策2:OfType(Of T) で型ごとにループを分割する
次に、可読性を重視したパターンです。LINQ の OfType(Of T) を使うと、そもそも Control を列挙する段階で型を絞り込むことができます。
' Label だけにテーマを適用
For Each lbl As Label In InputForm.Controls.OfType(Of Label)()
lbl.BackColor = Color.FromArgb(255, 1, 2, 3)
Next
' customButton だけにテーマを適用
For Each btn As customButton In InputForm.Controls.OfType(Of customButton)()
btn.BackColor = Color.FromArgb(255, 1, 2, 3)
Next
この書き方では、
- ループ変数そのものが
Label/customButton型 - キャストを明示的に書く必要がない
- 当然「同じ変数を複数回キャスト」もしない
ので、CA1800 の警告は発生しません。
OfType パターンの特徴
| 項目 | 内容 |
|---|---|
| 可読性 | 「Label のコレクションを回している」と一目で分かり、レビュー時に読みやすい。 |
| 拡張性 | 別の型を追加したくなったら OfType(Of 他の型) のループを一つ足すだけ。 |
| パフォーマンス | 内部的にはやはり型チェックとキャストを行うが、ビジネスアプリでは通常問題にならない。 |
| CA1800 対策 | そもそもキャストを書かないため、警告の出る余地がない。 |
個人的には、「処理を型ごとに分けて書きたい」「テーマ適用だけでなく他のロジックも増えてきた」という場合には OfType パターンを強くおすすめします。
解決策3:DirectCast を使う場合の正しい書き方
既存コードの都合などで DirectCast を使いたい場合でも、書き方を少し整えるだけで CA1800 を回避できます。
ポイントは、
- 条件分岐を
If ... ElseIfでつなぐ - 各ブランチの中でだけ
DirectCastを 1回だけ行う
という形にすることです。
For Each ctrl As Control In InputForm.Controls
If TypeOf ctrl Is Label Then
Dim lbl As Label = DirectCast(ctrl, Label)
lbl.BackColor = Color.FromArgb(255, 1, 2, 3)
ElseIf TypeOf ctrl Is customButton Then
Dim btn As customButton = DirectCast(ctrl, customButton)
btn.BackColor = Color.FromArgb(255, 1, 2, 3)
End If
Next
これなら、アナライザーから見ると
LabelのブランチではDirectCast(ctrl, Label)が 1回customButtonのブランチではDirectCast(ctrl, customButton)が 1回- 同じパス上で同一のキャストを繰り返しているわけではない
となるため、CA1800 の対象とはみなされません。
Select Case True パターン
If ... ElseIf が長くなってきたら、Select Case True を使っても構いません。
For Each ctrl As Control In InputForm.Controls
Select Case True
Case TypeOf ctrl Is Label
Dim lbl As Label = DirectCast(ctrl, Label)
lbl.BackColor = Color.FromArgb(255, 1, 2, 3)
Case TypeOf ctrl Is customButton
Dim btn As customButton = DirectCast(ctrl, customButton)
btn.BackColor = Color.FromArgb(255, 1, 2, 3)
' 他の型を追加したい場合はここに Case を足す
End Select
Next
この書き方でも「1ブランチにつき 1回だけキャスト」を守っていれば CA1800 は出ません。
ネストした子コントロールにもテーマを適用する再帰パターン
フォームによっては、Panel や GroupBox などのコンテナコントロールの中に Label や Button が入っていることがよくあります。この場合、単に InputForm.Controls を回すだけではすべてのコントロールに色が反映されません。
その際に便利なのが、再帰関数で子コントロールを辿るパターンです。先ほどの TryCast パターンを再帰にしたサンプルがこちらです。
Sub ApplyTheme(parent As Control, color As Color)
For Each c As Control In parent.Controls
Dim lbl As Label = TryCast(c, Label)
If lbl IsNot Nothing Then
lbl.BackColor = color
Continue For
End If
Dim btn As customButton = TryCast(c, customButton)
If btn IsNot Nothing Then
btn.BackColor = color
Continue For
End If
' 子コントロールを持つ場合は再帰的に適用
If c.HasChildren Then
ApplyTheme(c, color)
End If
Next
End Sub
フォーム側からは、次のように 1行で呼び出せます。
ApplyTheme(Me, Color.FromArgb(255, 1, 2, 3))
この関数でも、
TryCastは各型に対して 1回だけ- 結果を変数に持ち回す
- 再帰呼び出しのたびに
parent As Controlが変わるだけ
なので、CA1800 の警告とは無縁です。
再帰パターンでありがちな落とし穴
再帰でありがちな NG 例も挙げておきます。
Sub ApplyThemeBad(parent As Control, color As Color)
For Each c As Control In parent.Controls
If TypeOf c Is Label Then
DirectCast(c, Label).BackColor = color
End If
If TypeOf c Is customButton Then
DirectCast(c, customButton).BackColor = color
End If
If c.HasChildren Then
ApplyThemeBad(c, color)
End If
Next
End Sub
このようなコードは動作上は問題ないように見えますが、
- 同じ
cに対して複数のDirectCastが書かれている - 今後別の型が増えたときに、同じパターンをコピペする可能性が高い
などの理由で CA1800 が出やすく、保守性も下がります。やはり TryCast で一度だけキャストし、結果を変数に持つスタイルに統一しておくのが安全です。
設計レベルでの見直し:インターフェイス化やヘルパーメソッド
ここまでは「アナライザーに怒られない書き方」という観点で見てきましたが、規模が大きくなってきたら、そもそもの設計を少し変えるのも有効です。
IThemedControl インターフェイスを定義する
テーマ適用の責務をコントロール側に寄せるために、例えば次のようなインターフェイスを作っておく方法があります。
Public Interface IThemedControl
Sub ApplyTheme(color As Color)
End Interface
そして、テーマを適用したいコントロール(カスタムボタンなど)にこのインターフェイスを実装させます。
Public Class customButton
Inherits Button
Implements IThemedControl
Public Sub ApplyTheme(color As Color) Implements IThemedControl.ApplyTheme
Me.BackColor = color
Me.FlatAppearance.BorderColor = color
' 他にもフォントや前景色などをここで調整
End Sub
End Class
この状態であれば、フォーム側のテーマ適用は次のように書けます。
Sub ApplyTheme(parent As Control, color As Color)
For Each c As Control In parent.Controls
Dim themed As IThemedControl = TryCast(c, IThemedControl)
If themed IsNot Nothing Then
themed.ApplyTheme(color)
End If
If c.HasChildren Then
ApplyTheme(c, color)
End If
Next
End Sub
ここでも TryCast の「1回キャスト&結果を変数」が生きてくるため、CA1800 の警告は出ませんし、コントロールごとのテーマ適用ロジックをフォームから切り離せるメリットもあります。
拡張メソッドでヘルパー化する
同じようなテーマ適用処理を複数フォームで使う場合は、拡張メソッドとして切り出すのも手です。
Imports System.Runtime.CompilerServices
Public Module ThemeExtensions
<Extension()>
Public Sub ApplyThemeToControls(parent As Control, color As Color)
For Each c As Control In parent.Controls
Dim lbl = TryCast(c, Label)
If lbl IsNot Nothing Then
lbl.BackColor = color
End If
Dim btn = TryCast(c, customButton)
If btn IsNot Nothing Then
btn.BackColor = color
End If
If c.HasChildren Then
c.ApplyThemeToControls(color)
End If
Next
End Sub
End Module
フォームからは次のように呼び出せます。
Me.ApplyThemeToControls(Color.FromArgb(255, 1, 2, 3))
このように共通化しておくと、
- テーマ仕様の変更(色の計算、フォント変更など)を一箇所で管理できる
- CA1800 を含むコード分析の調整も一箇所で済む
というメリットが生まれます。
よくある NG コードと OK コードを比較する
最後に、CA1800 が出やすい例と、それを踏まえて整理した例を並べてみます。
| 目的 | NG コード例 | OK コード例 |
|---|---|---|
| シンプルなテーマ適用 | For Each ctrl As Control In InputForm.Controls If TypeOf ctrl Is Label Then DirectCast(ctrl, Label).BackColor = color End If If TypeOf ctrl Is customButton Then DirectCast(ctrl, customButton).BackColor = color End If Next | For Each ctrl As Control In InputForm.Controls Dim lbl = TryCast(ctrl, Label) If lbl IsNot Nothing Then lbl.BackColor = color Continue For End If Dim btn = TryCast(ctrl, customButton) If btn IsNot Nothing Then btn.BackColor = color Continue For End If Next |
| 型ごとに処理を分割 | For Each ctrl As Control In InputForm.Controls ' 型ごとに If を増やしていく… Next | For Each lbl As Label In InputForm.Controls.OfType(Of Label)() lbl.BackColor = color Next For Each btn As customButton In InputForm.Controls.OfType(Of customButton)() btn.BackColor = color Next |
| 再帰で子コントロールにも適用 | Sub ApplyThemeBad(parent As Control, color As Color) For Each c As Control In parent.Controls If TypeOf c Is Label Then DirectCast(c, Label).BackColor = color End If If TypeOf c Is customButton Then DirectCast(c, customButton).BackColor = color End If If c.HasChildren Then ApplyThemeBad(c, color) End If Next End Sub | Sub ApplyTheme(parent As Control, color As Color) For Each c As Control In parent.Controls Dim lbl = TryCast(c, Label) If lbl IsNot Nothing Then lbl.BackColor = color Continue For End If Dim btn = TryCast(c, customButton) If btn IsNot Nothing Then btn.BackColor = color Continue For End If If c.HasChildren Then ApplyTheme(c, color) End If Next End Sub |
まとめ:CA1800 警告は「設計の歪み」のサインと捉える
CA1800「不要なキャストを避ける」は、一見すると「アナライザーがうるさいだけ」と感じるかもしれませんが、よく見ると
- 同じ変数に対して何度もキャストしている
- 型判定とキャスト先の型が一致していない
- キャスト結果を使い捨てにしている
といった、将来的なバグやメンテナンス性の低下につながるコードパターンを教えてくれるサインでもあります。
本記事で紹介したように、
- TryCast で一度だけキャストし、結果を変数に保持する
- OfType(Of T) で最初から型ごとに列挙する
- DirectCast を使うなら If / ElseIf / Select Case True で 1ブランチ1キャストにする
- ネストしたコントロールには再帰+TryCast で対応する
- 必要に応じてインターフェイスや拡張メソッドで設計レベルから整理する
といったパターンを押さえておけば、CA1800 の警告は自然と解消されますし、WinForms のテーマ適用処理や UI ロジック全体もすっきりしていきます。
「警告を抑制する」のではなく、「警告をきっかけにコードを改善する」視点で、ぜひ一度自分のプロジェクトのコントロール走査処理を見直してみてください。

コメント