前回の記事で、VB6.0 で「Form の既定のインスタンス」を防ぐ方法について書きました。
VB2005 (VB8) で、Form の既定のインスタンス (Form の暗黙的なインスタンス化) が復活してしまったので、それを防ぐ方法を考えたいと思います。
デフォルトでは、以下のようにコードから「Form の既定のインスタンス」の実体である、My.Forms 配下にアクセスできます。
My.Forms (Form の既定のインスタンス) にアクセス可能
できれば「コンパイル解決」したいので、いっそ使えなくしてしまいましょう。vbproj ファイルを、テキスト エディタなどで開いてみてください。
変更前の vbproj ファイル
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{????????-????-????-????-????????????}</ProjectGuid>
<OutputType>WinExe</OutputType>
<StartupObject>Sub Main</StartupObject>
<RootNamespace>WindowsApplication1</RootNamespace>
<AssemblyName>WindowsApplication1</AssemblyName>
<MyType>WindowsForms</MyType>
<MyType>WindowsFormsWithCustomSubMain</MyType>
</PropertyGroup>
<PropertyGroup> 要素内に <MyType> 要素があります。<MyType> 要素の中身は、アプリケーション フレームワークが有効である場合は "WindowsForms" が、そうでない場合は "WindowsFormsWithCustomSubMain" が格納されています。この <MyType> 要素の中身を、次のように "Empty" に変えます。
Form の既定のインスタンスと My を防いだ状態の vbproj ファイル
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{????????-????-????-????-????????????}</ProjectGuid>
<OutputType>WinExe</OutputType>
<StartupObject>Sub Main</StartupObject>
<RootNamespace>WindowsApplication1</RootNamespace>
<AssemblyName>WindowsApplication1</AssemblyName>
<!-- <MyType>WindowsForms</MyType> -->
<!-- <MyType>WindowsFormsWithCustomSubMain</MyType> -->
<MyType>Empty</MyType>
</PropertyGroup>
こうすることで、My 自体の使用を防ぐことができます。その結果、Form の既定のインスタンスの使用も防ぐことができるわけです。
My.Forms (Form の既定のインスタンス) にアクセス不可
My 自体の使用を禁じると、プロジェクトのプロパティにある「アプリケーション フレームワークを有効にする」が非活性になることに注意してください。
「アプリケーション フレームワークを有効にする」が非活性になる
Form の既定のインスタンスの使用だけを封じて、My が使用できるようにするには以下のようにします。ただし、この方法ですとコンパイル解決 (コンパイル時にチェック) することができません。
VB2005 - フォームの「既定のインスタンス」の使用を実行時に防ぐ
Option Strict On
Public Class Form2
Public Sub New()
' この呼び出しは、Windows フォーム デザイナで必要です。
Me.InitializeComponent()
' InitializeComponent() 呼び出しの後で初期化を追加します。
AddHandler Me.HandleCreated, Addressof Form_HandleCreated
End Sub
Private Shared Sub Form_HandleCreated(ByVal sender As Object, ByVal e As System.EventArgs)
If sender Is My.Forms.Form2 Then
Throw New System.InvalidOperationException("既定のインスタンスを使用するんじゃあないッ!!")
End If
End Sub
End Class
既定のインスタンスと同一のインスタンスであった場合は、InvalidOperationException 例外をスローします。
引数 sender とのインスタンス比較に、My.Forms コレクション内のメンバを使っているのは、こちらの方が '型 (Form2)' との区別がついて見やすいからです。既定のインスタンスの実体 (Form2) は、My クラスの Forms コレクションのメンバ (My.Forms.Form2) と同一になります。
VB2005 (VB8) では、同じ型のインスタンス内から、フォームの既定のインスタンスを参照することができません。そのため、HandleCreated イベントのシグネチャが、共有メンバ (Shared メンバ) になっています。
これでは、わざわざすべての Form クラスに記述が必要になってしまい、実用的ではありません。ですから、次のようにリフレクションを使って実装した方が実用的であると言えます。リフレクションを使って動的にイベントを関連付けていますので、アセンブリ (プロジェクト) にどれだけフォームを追加したとしても問題ありません。
VB2005 - フォームの「既定のインスタンス」の使用を実行時に防ぐ (リフレクション版)
Option Strict On
Public Class Program
' エントリ ポイント
<System.STAThread()> _
Public Shared Sub Main()
' フォームの「既定のインスタンス」をすべて HandleCreated イベント ハンドラへ動的に追加
AddHandlerFormsHandleCreated()
' アプリケーションを Form1 から開始する
System.Windows.Forms.Application.Run(New Form1())
End Sub
' すべての「既定のインスタンス」の HandleCreated イベントを設定
Private Shared Sub AddHandlerFormsHandleCreated()
Dim oType As System.Type = GetType(My.MyProject.MyForms)
Dim oProperties As System.Reflection.PropertyInfo() = oType.GetProperties()
For Each oProperty As System.Reflection.PropertyInfo In oProperties
Dim oObject As Object = oProperty.GetValue(My.MyProject.Forms, Nothing)
If TypeOf oObject Is System.Windows.Forms.Form Then
Dim oForm As System.Windows.Forms.Form = DirectCast(oForm, System.Windows.Forms.Form)
AddHandler oForm.HandleCreated, AddressOf Forms_HandleCreated
End If
Next oProperty
End Sub
' すべての「既定のインスタンス」の HandleCreated イベント
Private Shared Sub Forms_HandleCreated(ByVal sender As Object, ByVal e As System.EventArgs)
Throw New System.InvalidOperationException("既定のインスタンスが使用されました。")
End Sub
End Class
VB の言語設計者は "フォームは、単一のインスタンスである場合が多い" ことを理由に「既定のインスタンス」を復活させました。しかし、どこからでもアクセスできるのは、(特にグループ開発のことを考えて) デメリットでもあるという点も考慮して欲しかったです。既定のインスタンスを設けること自体には、もはや反対はしませんが、プロジェクトのプロパティなどで「既定のインスタンスの使用を禁止する」というオプションがあっても良いのではないでしょうか?
Microsoft Connect : Visual Studio and .NET Framework (Product Feedback Center) に、以下のようなフィードバックがあります。
The removal of the default instance in the initial versions of .NET was a bold move, but one that *should* have been done.
So why re-introduce them ?
They helped a little in getting newbies started, but this caused no end of hard-to-debug problems later on, and a lot of professional developers generally attempt to remove any use of them when touching the resulting code, or refuse to touch the code altogether.
意訳しますと「既定のインスタンスを再導入しないでください!!」と訴えているわけですね。さらには、255 人もの方が高いレートで Vote しています。(平均 4.62 Rate)
これとは別に「プロジェクトのプロパティなどで '既定のインスタンスの使用を禁止する' というオプションを設けてください」というフィードバックをしようかと迷っています。皆さん、投票してくれるでしょうか... (;^-^)