佐々木屋

技術的なことから趣味まで色々書きます

Option Strict Off は悪なのか?

とある方からこんな質問。

「Option Strict Off は使うべきか」

私の判断は、「どちらでも良い」が正解です。
他のWEBや本を見ると、「On」にしないと駄目プラグラマやで~みたいな風潮で洗脳するように書かれていますが・・・。

そもそも、「Option Strict」って実はVB.NET特有の機能です(多分)。C#Javaにはありません。なぜこの機能があるかというと、理由は2つあります。

初心者向けとVB6ユーザーへの忖度

一つは、先日C#とVB.NETの違いでも書きましたがVB.NETは他の言語に比べて文法規制が非常にゆるいです。逆にC#C++などは文法規制が非常に厳しいので、初心者はここでつまづきます。ということは逆に言えば、VB.NETは初心者でも非常に簡単に学習できるということ
にもなります。この差は間違いなく「Option Strict」があるかないかです。

これは悪い意味ではなく、広くプログラミングを学んでもらう、VB6ユーザーがVB.NETへ気楽に移行できるように、という点を考えれば非常に有用であると私は思います。そもそも全員が全員ハイレベルなプログラマになる必要はないので。

言語仕様の問題

もう一つの理由はVB.NET言語仕様です。例えば以下のコードを考えます。

Dim hour1 As Integer = 2
Dim minutes1 As Integer = 45
Dim res As Integer = hour1 * 40 + minutes1 * 2 / 3 'この行が縮小変換でエラー

このコードはVB.NETだと右辺がDouble型と判断され、左辺がInteger型なので縮小変換となってしまいエラーとなります。

ではC#ではどうなるかというと、

int hour1 = 2;
int minutes1 = 45;
int res= hour1 * 40 + minutes1 * 2 / 3; //左辺右辺どちらもint型になる

このように右辺もちゃんとint型と判断されて型変換無しで普通に書くことができます。こういった現象が様々な場所で出てきます。

まとめ?

確かに「型」の認識は正しく身に着けた方が良いですが、VB.NETの言語仕様が少し斜め上を行っているので、状況によっては「Option Strict Off」を利用した方が良いのでは?という考えに達するのも致し方ないと思うのです。

但し、今後他のプログラミング言語を勉強したい、VB.NETプログラマだけどより高みを目指したい、ということであれば「Option Strict On」の方が色々身につくのは間違いないです。

「Option Strict On にすると可読性が落ちてしまうんですけど!」

姉さん事件です(古)。新たな問題です。仮にOption Infer Onした時、以下の質問がありました。

結果、自分の知識が足りないため、異常にコードが長くなってしまい、全体が読みにくくなってしまう傾向にあります。※可読性が落ちて、混乱します。

これはある程度経験が物を言うということもありますが、恐らく「Option Strict On」で発生する問題のほとんどは縮小変換による「型」問題です。これを解消するのは、例えばInteger型の型変換関数を静的クラス(モジュール)に作ってしまってそれをバンバン使う、という事をします。これはクラス関数のお作法としての、

  • 受け入れは心大きく
  • 送り出しは厳密に

にも沿っています。

慣れてくると変数の型設定やクラスの作り方が「正しい型」ありきの設計になってきます(多分)。

本当のまとめ

3行にまとめると、

  • 「Option Strict Off」は一概に悪とは言えない
  • 型の認識がしっかりしているのであればOffでも良い(というかOffせざるを得ない)
  • でも、より高みを目指すならOnの方が後々幸せになれるよ

文字列分割(Split)の挙動

C#VB.NETでは文字列分割の挙動で若干の違いがあります。これはVB.NET初学者でもよく間違いますので、しっかり覚えておきましょう。
例えば、文字列「lowhogemiddlehogehighhogenone」を「hoge」で分割することを考えます。分割後を列挙した場合、以下のような結果になることが目標です。

low
middle
high
none


VB.NETのみ】String.SplitメソッドとSplit関数の違い

VB.NETではString.SplitメソッドとSplit関数の2種類の方法があります。後者のSplit関数はVB独自の記法でVB6時代から継承された関数です。
普通にSplit関数を利用すれば問題なく分割することができます。

Dim res1 As String() = Split(naiyo, "hoge")
low
middle
high
none

また別の機会にも書きますが、本来VB.NETの環境下において、VB6名残の機能は使用しない方が良いと言われていますので、StringクラスのSplitメソッドで書き換えます。そうすると、結果はどうなるでしょうか。

Dim res1 As String() = naiyo.Split("hoge")
low
ogemiddle
oge
ig

ogenone



本来StringクラスのSplitメソッドは、区切り文字をchar型にする必要があります。それが.NET Framework2.0よりstring型を渡せるようになりましたが、この場合配列で渡す必要があります。しかし、VB.NETの場合はVB6ユーザーへ忖度したため、string型でも配列にする必要性を省いてしまいました。結果、string型の一文字目をchar型と勝手に認識するようにして、最初の「h」のみを区切り文字にしてしまったのです。
そうすると、区切られる文字列に「h」が含まれていると、そこまで区切ってしまいます。上記の場合「high」という単語の「h」に反応してしまった形です。

この場合は以下のようにstring型の配列で渡す必要があります。string型(配列)をSplitメソッドに渡した場合は、StringSplitOptionsオプションを第二引数に入れないとコンパイルエラーになります。

'空要素を排除する場合はStringSplitOptionsにRemoveEmptyEntriesを指定する
Dim res1 As String() = naiyo.Split(New String() {"hoge"}, StringSplitOptions.None)

なお、C#の場合はstring型を引数に渡すことはできませんので、VB.NETのようなおかしな動作になることはありません。

他の方法

char型であれば特に不自由なく分割できますが、例のようにstring型だと配列にしてあげれば問題なく分割できます。
文字列分割は他にもいくつか方法がありますので、予備知識として少し紹介しておきます(基本はString.Splitメソッドで問題ない)。

System.Text.RegularExpression名前空間Regexクラスによる分割

RegexクラスのSplitメソッドでも文字列を分割できます。Stringクラスよりは速度は遅めです。

string[] res1 = System.Text.RegularExpressions.Regex.Split(naiyo, "hoge");
Dim res1 As String() = System.Text.RegularExpressions.Regex.Split(naiyo, "hoge")


ReplaceメソッドとSplitメソッドの合わせ技

文字列に絶対含まれないchar型の文字が簡単に推測できるなら有用な方法です。Replaceメソッドで一旦区切り文字を適当なchar文字に変換した後にSplitメソッドにそのchar文字を渡す方法です。速度は以外と早く、RegexクラスのSplitメソッドより高速です。

string[] res1 = naiyo.Replace("hoge", ",").Split(',')
Dim res1 As String() = naiyo.Replace("hoge", ",").Split(",")


C#のみ】Microsoft.VisualBasic.Strings.Split関数による分割

VBのSplit関数機能を利用して分割する方法です。速度はとてつもなく遅いのでおすすめしません。

string[] res1 = Microsoft.VisualBasic.Strings.Split(naiyo, "hoge");

IPアドレスを取得する

IISによるASP.NETIPアドレスを取得する方法です。
System.Web名前空間のHttpContext.Current.Requestプロパティを利用します。

string ipAddress = HttpContext.Current.Request.UserHostAddress;

//以下でも取得可能
ipAddress = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
Dim ipAddress As String = HttpContext.Current.Request.UserHostAddress

'以下でも取得可能
ipAddress  = System.Web.HttpContext.Current.Request.ServerVariables("REMOTE_ADDR")

開発環境で実行すると、「localhost」、又はIPv4の場合であれば「172.0.0.1」、IPv6の場合であれば「::1」が返ります。

マスターページを含むページイベント処理の順番

マスターページを含む場合、マスターとコンテンツのInitとLoadの順番が逆になるので、何か共通処理をマスターページに処理させる場合はコンテンツの処理と間違えないように注意して下さい。
なお、今回はGlobal.asaxの処理は入れません。別の機会(ロギング処理の流れ等)で触れようと思います。

順番 区分 イベント 処理
1 コンテンツ PreInit ページ初期化前状態。マスターページより先に処理されるので、ページ独自処理や動的マスターページの作成を行う。
2 マスターページ Init ページ初期化処理。
3 コンテンツ Init ページ初期化処理。データベースオープン処理等はここで行う。
4 コンテンツ InitComplete ページ初期化処理完了。
5 コンテンツ PreLoad ページロード開始前状態。ViewStateが復活する。
6 コンテンツ Load ページロード開始。
7 マスターページ Load マスターページロード開始。
8 コンテンツ 変更系(Change)イベント
9 コンテンツ 操作系(Click)イベント
10 コンテンツ LoadComplete ページロード完了。
11 コンテンツ PreRender ページオブジェクト読込前の状態。サーバーコントロールのViewState値の変更が可能。
12 マスターページ PreRender マスターページオブジェクト読込前の状態。
13 コンテンツ PreRenderComplete ページ表示開始前状態。
14 マスターページ Unload サーバーコントロールがメモリから解放される状態。各インスタンスの破棄処理を記述。
15 コンテンツ Unload サーバーコントロールがメモリから解放される状態。データベース接続解除処理や各インスタンスの破棄処理を記述。

ユーザーアカウントを起動時に表示させない

パソコンに登録されているユーザーアカウントを生かした状態でユーザー側に見えないようにすることができます。
内部的処理や管理者がリモートで使用するようなアカウントはこの設定をしておくと良いでしょう。

//場所
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList

//キー名
アカウント名

//値(DWord)
0:非表示 ←これを設定
1:表示

非同期処理、マルチスレッド(Task②戻り値、引数がある場合)

前回はTaskクラスの実行方法を説明しました。メソッドは戻り値、引数が無いvoidを例としていますが、戻り値や引数がある場合のメソッドをTaskに実行させることも可能です。

なお、今回のコードはRunメソッドを採用します。Factory.StartNewメソッドでも書き方は一緒です。.NET Framework4.5未満の方は、Factory.StartNewに変更して使って下さい。

また、メソッドの指定はラムダ式で書いています。VB.NETに匿名メソッドが無い為、ラムダ式を採用しました。お好きな方でどうぞ。

戻り値があるメソッドのTask実行

private string HeavyProc() {
    for (int i = 0; i <= 5; ++i) {
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine("Hoge");
    }
    return "HogeHoge";
}

public static void Main() {
    Task<string> t = Task<string>.Run(() => HeavyProc());
    Console.WriteLine(t.Result);
}
Private Function HeavyProc() As String
    For i As Integer = 0 To 5
        System.Threading.Thread.Sleep(1000)
        Console.WriteLine("Hoge")
    Next
    Return "HogeHoge"
End Function

Public Shared Sub Main()
    Dim t As Task(Of String) = Task.Run(
        Function()
            Return HeavyProc()
        End Function)
    Console.WriteLine(t.Result)
End Sub


戻り値が無く、引数がある場合

private string HeavyProc(string naiyo) {
    for (int i = 0; i <= 5; ++i) {
        Thread.Sleep(1000);
        Console.WriteLine(naiyo);
    }
}

public static void Main() {
    Task t = Task.Run(() => HeavyProc("Hoge"));
}
Private Function HeavyProc(ByVal naiyo As String) As String
    For i As Integer = 0 To 5
        Thread.Sleep(1000)
        Console.WriteLine(naiyo)
    Next
End Sub

Public Shared Sub Main()
    Dim t As Task = Task.Run(
        Sub()
            HeavyProc("Hoge")
        End Sub)
End Sub


戻り値があり、引数もある場合

private string HeavyProc(string naiyo) {
    for (int i = 0; i <= 5; ++i) {
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine(naiyo);
    }
    return "HogeHoge";
}

public static void Main() {
    Task<string> t = Task<string>.Run(() => HeavyProc("Hoge"));
    Console.WriteLine(t.Result);
}
Private Function HeavyProc(byval naiyo As String) As String
    For i As Integer = 0 To 5
        System.Threading.Thread.Sleep(1000)
        Console.WriteLine(naiyo )
    Next
    Return "HogeHoge"
End Function

Public Shared Sub Main()
    Dim t As Task(Of String) = Task.Run(
        Function()
            Return HeavyProc("Hoge")
        End Function)
    Console.WriteLine(t.Result)
End Sub

フォームイベントの発生順序

フォームイベントの発生順番をまとめました。実務レベルで必要な部分は青字になりますので、そこだけ理解しておけば問題ありません。

起動時のイベント発生順番

順番 イベント 内容
1 Control.HandleCreated コントロールに対してハンドルが作成されると発生
2 Control.BindingContextChanged コントロールがバインドした時に発生
3 Form.Load フォームが初めて表示される直前に発生
★コントロールの初期化処理などを記述
4 Control.VisibleChanged Visible プロパティの値が変更された場合に発生
5 Form.Activated フォームがコード内、ユーザー操作によってアクティブになると発生
6 Form.Shown フォームが初めて表示されるたびに発生
★時間がかかる処理などを記述

終了時のイベント発生順番

順番 イベント 内容
1 Form.Closing フォームが閉じている間に発生
★「e.Cancel = true」で終了命令のキャンセルが可能
2 Form.FormClosing フォームが閉じる前に発生
3 Form.Closed フォームが閉じたときに発生
インスタンス破棄やフォーム終了後の後処理を記述
4 Form.FormClosed Visible フォームが閉じた後に発生
5 Form.Deactivate Visible フォームがフォーカスを失い、アクティブでなくなると発生