Visual Basic 初級講座
VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

 

Visual Basic 中学校 > 初級講座 >

第36回 型の指定

今回は地味です。型の指定方法による問題点とそれらの解決方法を説明します。

概要

・Object型などの総称型はいろいろな型の値をセットできて便利だが、プログラムのミスが増えるし、速度面で劣るのでできるだけ使用しない。

・総称型から特定の型への変換に暗黙的な型変換を使用すると速度が遅くなる。

・型変換のためにCIntやCStrなどさまざまな関数が用意されている。特にDirectCastは高速で動作する。

・Option Strict Onにすると自動的に型チェックが行われるので便利。しかし、Option Strict Offをお勧めする。

 

1.Object型の問題点

 

VBをある程度使った経験があるならもはや常識となっている「型」。型には文字列型や数値型などいくつかありますが、この「型」とのうまい付き合い方を紹介するのが今回のテーマです。

「型」とはデータの構造のことです。コンピュータは最終的にはすべてのデータを「0」と「1」の羅列で区別します。ですから、コンピュータにとっては文字も数値もクラスも構造体も同じようなものなのですが、文字が必要とされた場合はこの「0」と「1」の羅列を文字として解釈し、数値が必要とされた場合は数値として解釈する必要があります。

この解釈方法こそが型であるとも言えます。

しかし、解釈方法の違いはあるにせよ、どのような場合でも「0」と「1」の組み合わせに過ぎないことからあらゆるデータをたった1つの型で表現するという方法もあります。その型こそObject型です。

この説明を読んだ方は、「あぁObject型というのは0と1の組み合わせを保存するだけのシンプルな型なのか」と思ってしまうかもしれませんが、実際のObject型は「0」と「1」の組み合わせには違いありませんが後でデータを効率よく取り出すためにもっと複雑な構造をしています。

けれどプログラマはこのような複雑な構造のことを一切考えないで手軽にObject型を使用することができます。

次のコードは有効なコードです。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim X As Object
Dim
Y As
Object

'▼Object型を数値型として扱う

X = 5
Y = 6
MsgBox(X + Y)

'▼Object型を文字列型として扱う

X = "Hello, "
Y = "Object!"
MsgBox(X & Y)

'▼Object型をフォームとして扱う

X = Me
MsgBox(X.Text)

■リスト1

このコードでは変数Xと変数Yがある時は数値型、またある時は文字列型やフォームとして変幻自在にふるまっています。

Object型にはあらゆるデータをセットすることが可能なのでこのような使用方法もできるわけです。

とは言え、このような使い方は推奨されていません。理由は2つあります。1つはプログラムのミスを防ぐため、もう1つはプログラムの処理速度を向上させるためです。

たとえば、数値をセットすべきところに文字をセットしたとすると通常の場合はエラーになるのですぐに気が付きますが、Object型を使用しているとエラーにならないので気が付くのが遅れる可能性があります。また、数値を代入した場合にそれがInteger型となるのかSingle型やDouble型・Decimal型となるのか気を配る必要が出てきます。Single型とDouble型は浮動小数点型なので計算に誤差が発生しますからこれは大きな問題です。

このようなことを気にしながらちまちまプログラムするよりは初めからはっきり型を指定した方が良いのです。

もう1つの処理速度の向上については次の例を見てください。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim i As Integer
Dim
Start As
Long
Dim X As Object
Dim Y As Integer

'▼Object型の場合

X = 100
Start = Now.Ticks
'処理開始前の時間を記録

For i = 0 To 1000000
    X += 1
Next

MsgBox(Now.Ticks - Start) '処理にかかった時間を表示

'▼Integer型の場合

Y = 100
Start = Now.Ticks
'処理開始前の時間を記録

For i = 0 To 1000000
    Y += 1
Next

MsgBox(Now.Ticks - Start) '処理にかかった時間を表示

■リスト2

この例ではObject型の変数とInteger型の変数を使ってまったく同じ処理をしています。違うのは変数の型だけです。しかし、Integer型の変数を使うと計測不可能なほど瞬時に終了する処理がObject型で行うとある程度の時間がかかることがわかります。

Object型の場合 Integer型の場合
2,000,000 46,875

■表1:実験結果 10回の試行の平均値

※実験に使用した私のマシンはCPU2GHZです。もっと高性能なマシンの場合は結果に差が出ない可能性もあります。

これはシンプルな例で計測していますが、複雑なアプリケーション内で使用する場合はもっとこの差が顕著に現れる場合があります。

以上の2つの理由をみただけでObject型を使うべきでないことはあきらかでしょう。

それでもどうしてもObject型を使用したい場面がいくつかあります。1つはどのような型が必要になるのかわからない場合です。そのような場合は確かにあります。が、そのような場合はその都度説明していくことにするので今回は割愛します。

また別のケースとしてはコレクションを使用する場合が挙げられます。 コレクションについては初級講座第28回 コレクションで詳しく説明しています。配列のようなものでたくさんの変数をひとまとまりとして扱う場合に使用するものでした。具体的にはArrayListHashTableなどが存在します。

ジェネリック(※1)なしでコレクションを使用すると値がすべてObject型となるので上述のようなObject型の問題が発生します。この問題とその回避方法も初級講座第28回で説明していますが、ここで簡単に繰り返すと、要するに「型を指定しなさい」ということでした。

※2:ジェネリックとはVB2005から登場した新しい型の指定方法でDim Lst As New List(Of Integer)のようにOfを使ってする型指定方法です。

 

2.総称型

 

型の指定方法については後で詳しく説明するとします。先にObject型以外のどの型が問題となるのか考えて見ます。 この型の問題が発生するのはObject型だけではありません。

つまるところ、「実際には何型だかすぐにはわからない型」を使用する場合に問題になるわけです。

Integer型にはInteger型しかセットできないのでこのような問題が発生しないことは明らかです。ちなみにInteger型にSingle型の値をセットすると強制的にInteger型に変換されてしまいます。

ここでクラスの「継承」が関係してきます。「継承」については初級講座では詳しく解説しませんが、簡単に説明すると「親クラスの機能をひきついだ子クラスを作成する機能」です。

たとえば、Integer型ですが、Integer型の.NET Frameworkでの名称はInt32構造体です。IntegerInt32構造体は名前が違うだけで同じものです。このInt32構造体の継承元となっている親クラスはValueTypeクラス(読み方:ValueType = バリュータイプ)です。

次のコードで実験してみるとValueTypeIntegerを代入できることがわかります。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim i As Integer
Dim
v As ValueType

i = 627
v = i

MsgBox(v)

■リスト3

念のためにString型で同じことをやってみるとビルドエラーになります。

ところが、Single型では同じことができてしまいます。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim s As Single
Dim
v As ValueType

s = 6.27
v = s

MsgBox(s)

■リスト4

これはSingle型の親クラスもValueTypeクラスだからです。実のところValueTypeクラスはすべての構造体の親クラスとなっているのです。ですから、このValueType型の変数にはDouble型やData型をはじめColor型など を代入することができます。

となるとValueType型の変数は「実際には何型だかすぐにはわからない型」ですからObject型と同様の問題があることになります。

ValueType以外にも継承関係にあるクラスは無数にありますから同じ問題は随所に見られます。 たとえば、コントロールの継承関係は次のようになっています。

■図1:コントロールの継承関係

この図ではたとえば、ButtonBase型の変数にはButtonCheckBoxRadioButtonが代入できることがわかります。この図は初級講座第17回 コントロールの基本共通機能に掲載したものを再掲したものです。

このようにいくつかの異なる型の値をセットできる型のことを総称型と呼びます。

 

博士のワンポイントレッスン  親クラスを調べるには
V太:博士〜。親クラスを調べるにはどうしたらいいですか?たとえば、Graphicsクラスの親クラスは何ですか?
博士:基本的なことじゃよV太君。MSDNライブラリじゃ。
えー。だってMSDNライブラリってどこに何が書いてあるかわかりにくいんですよねー。そりゃすみからすみまで読めばGraphicsクラスの親クラスもわかるんだろうとは思いますけど・・・。
B子:なまけたこと言ってるんじゃないわよ!普段からMSDNライブラリを使っていれば必要な情報の位置はだいたいわかるわ。努力が必要なのよ!
楽する方法もあるぞ。F1を押すと自動的に該当するヘルプが表示される機能を利用するんじゃ。たとえば、Dim g As Graphicsと書いてある場合、「Graphics」のところにカーソルを移動してF1を押すのじゃ。(※「カーソル」とはテキストを入力するときの I の形をしたカーソルのことです。マウスカーソルのことではありません。)
どれどれ。おっ。Graphicsクラスのメンバの一覧がでてきました! 親クラスはどこに書いてあるかな?
実はここからは「慣れ」なのじゃ。一番上にある「Graphics 概要」のリンクをクリックするとわかると思うが、最近のバージョンのMSDNライブラリでは上のほうにある「Graphics」というリンクをクリックするのじゃ。MSDNライブラリもいい加減に安定した表記を採用してほしいのぉ。
それに苦労してたどり着いても最新のバージョンのMSDNライブラリの場合は「Inherits」キーワードの意味がわかっていなければ親クラスを探すことはできないのじゃ。
ふーん。どうやらGraphicsクラスの親クラスはMarchalByRefObjectというクラスのようですね。
他にも「オブジェクトブラウザ」を使って調べることもできるわ。

 

3.型変換

 

さて、総称型の値を使用するときに何も考えないで使用するとVBが実際の型を自動判定して使用するので「自動判定」の分だけ余計に時間がかかります。これが最初に確認したObject型の速度の問題です。

この問題を解決するには自動判定に頼らないで自分で型を指定することです。これはある型から別の型へ型を変換することになりますから「型変換」と呼ばれます。

VBでは型変換のためにたくさんの関数が用意されています。主なものを挙げます。

関数 読み方 機能
メソッド CStr シーストリング String型に変換する。
メソッド CInt シーイント Integer型に変換する。
メソッド CDec シーデク Decimal型に変換する。
メソッド CDate シーデイト Date型に変換する。
メソッド CDbl シーダブル Double型に変換する。
メソッド CType シータイプ 任意の型に変換する。
メソッド DirectCast ダイレクトキャスト 総称型から指定の型への直接割り当て。

■表2

最初に紹介したObject型の速度テストの例もこれらの型変換関数を使用すると大分機能を向上させることができます。今度は次のコードでテストしてみてください。違いがわかります。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim i As Integer
Dim
Start As
Long
Dim X As Object

'▼自動型変換の場合

X = 100
Start = Now.Ticks
'処理開始前の時間を記録

For i = 0 To 1000000
    X += 1
Next

MsgBox(Now.Ticks - Start) '処理にかかった時間を表示

'▼明示的な型変換の場合

X = 100
Start = Now.Ticks
'処理開始前の時間を記録

For i = 0 To 1000000
    X =
CInt(X) + 1
Next

MsgBox(Now.Ticks - Start) '処理にかかった時間を表示

■リスト5

この例のようにInteger型がセットされていることがわかっている場合はさらにDirectCast関数を使用 して速度の向上を図ることができます。

VB.NET2002対応 VB.NET2003対応 VB2005対応

'▼DirectCastの場合

X = 100
Start = Now.Ticks
'処理開始前の時間を記録

For i = 0 To 1000000
    X = DirectCast(X, Integer) + 1
Next

MsgBox(Now.Ticks - Start) '処理にかかった時間を表示

■リスト6

この例はCIntを使用する場合よりもさらに高速です。DirectCastはあらゆる場面で他の変換関数よりも高速に動作します。

とは言え、やはりはじめからInteger型で宣言した変数を使うのが一番早いです。 最初の実験結果もまとめて表にしておきますので参考にしてください。

Object型の場合 CIntの場合 DirectCastの場合 Integer型の場合
2,000,000 1,031,250 671,875 46,875

■表3:実験結果2 10回の試行の平均値

これらの型変換関数は総称型から特定の型へ変換する以外にも特定の型から特定の型へ変換するのに使うこともできます。ただしDirectCast関数だけは総称型から本来の型への変換しかできません。

たとえば、CInt関数を使うと文字列型をInteger型に変換することができます。文字列型からInteger型への変換は特に関数を使わなくても単に値をセットするだけで行えますから普段は当たり前のように感じているでしょう。この場合でも明示的な型変換を使用したほうが効率が良いということです。

 

4.型指定の強制

 

さて、このように「型」を明示的に指定することはプログラムの性能向上に直結しますのでVBとしては、プログラマにいくつかのチェック機能を提供しています。

たとえば、次のプログラムを見て明示的に方が指定されていない部分がどこにあるか指摘できますか?

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim X As Integer
Dim
Y As
Integer

X = 5
Y = 6

TextBox1.Text = X + Y

■リスト7

プログラムが短いですからすぐにわかるでしょう。最後の行が暗黙的な型変換を行っています。X + Yの結果はInteger型ですが、これを文字列型であるTextBox1Textプロパティにセットしていますから、ここでInteger型から文字列型への暗黙の変換が行われるわけです。

この変換を明示的に行うには次のようにします。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim X As Integer
Dim
Y As
Integer

X = 5
Y = 6

TextBox1.Text = CStr(X + Y)

■リスト8

この方が性能の良いプログラムとなります。

もっと複雑なプログラムではこのような場面に気が付かない可能性がありまし、いちいち意識しながらプログラムするのも大変です。そこでVBにはこれらのチェックを自動で行う機能があります。

Option Strict(読み方:Option Strict = オプション ストリクト)をOnにすると自動チェックが行われます。

上記の例からCStrをはずしてもとの状態に戻してください。そして、プログラムの一番先頭に次の1行を追加してください。

VB.NET2002対応 VB.NET2003対応 VB2005対応


Option
Strict On
 

■リスト9

そうするとどうでしょうか。X + Y の部分にたちまち青い波線がでてきて暗黙の型変換が行われることを知らせてくれます。

もし、VB2005を使用しているならスマートタグとともにどのように修正すればよいかも教えてくれます。

■画像2:VB2005の便利な修正候補機能

Option Strict Onの状態ではこの状態を修正するまでビルドができません。ですから、あなたが厳格なプログラムを望んでいるのなら常にOption Strict Onを指定することをお勧めします。私は厳格なプログラムを望んでいないのでOption Strict Onを指定していませんが…。

いちいち自分でOption Strict Onと記述しなくても既定値として設定することもできます。[プロジェクト]メニューで[(プロジェクト名)のプロパティ]をクリックすると表示される画面で、「コンパイル」タグ(VB.NET2003以前の場合は「ビルド」タグ)を使用するとこの設定を行うことができます。

■画像3:Option Strictの既定値の設定

■画像4:VB.NET2003以前の場合

この設定を行うとすべてのファイルでOption Strict On状態になります。ただし、自分でOption Strict Offと記述した場合はそのファイルだけは例外となります。

Option Strict Onよりはゆるやかにプログラムしたいが、宣言した変数のみ使用したいという場合はOption Explicit On(読み方:Option Explicit = オプション エクスプリシット)を使用することもできます。

この設定は既定値がOnなので特に意識したことがないかもしれませんが、実のところOption Explicit Offの設定下では宣言なしに変数を使用することができてしまいます。宣言していない変数はすべてObject型とみなされます。

この設定はVB6との互換性のために残されている設定ですので必要がなければOffにしないようにしてください。

 

5.Option Strict問題

 

Option StrictはVB.NET2002以降で追加された機能です。VB6以前ではこのような機能はなく常にOption Strict Offと同じ状態になっていました。機能が新しく追加されたということはその機能が必要とされているということです。

それでは我々はOption Strict Onを使用すべきでしょうか?またVBプログラマの多くはOption StrictOnにしているのでしょうか?

もちろん最終的には個人の好みですが、そんなことを言い出したら何だって最終的には個人の好みなのですからこれで満足のいく結論というわけにはいきません。

まず、現状ですが私の感覚では多くのプログラマはOption StrictOffにしているようです。これは多分、VBインストール時の既定の設定がそうなっているというのと、VB6がそうであったということによるでしょう。

ということは逆にOption Strict Onを採用しているプログラマは明確な意思を持ってOnにしているということになります。そして今回説明したようにOption Strict Onには性能面での重要なメリットがあります。

そのため、Option Strict Onを採用しているプログラマの一部にはOption Strict Offを採用しているプログラマを素人扱いするような風潮があるようです。実際に私はそういう人の話を聞いたことがあります。(あくまで「一部」です。私が出会った大部分のプログラマはこのようなスタンスの違いがあってもそれを理解して受け入れる深みのある人たちでした。)

VB2005でのジェネリックの導入でもわかるように現在のプログラム言語の流れは「できるだけ事前に型を決定する」ということにあるようです。このことはOption Strict Onを採用すべきである根拠になりそうです。

それらすべてのことを理解した上で私はOption Strict Offを採用しています。また、みなさんにもOffをお勧めします。

私の考えではOption Strict OffこそがVBらしさの1つなのです。たとえば、C#やJavaなどにはそもそもこのようなオプションはなく常にOption Strict Onと同じ状態です。

C#では次のコードはどうやってもビルドできません。

int X;
int Y;

X = 5;
Y = 6;

textBox1.Text = X + Y;

■リスト10:C#の例

このコードがビルドできないのは最後の行で文字列型であるTextプロパティに数値型をセットしようとしているからです。ですが、人間的にはどうですか?何をやろうとしているか明確なんだから気をきかせて自動的に解釈してくれても良さそうだと思いませんか?

その気を利かせてくれる機能こそがOption Strict Offです。そしてこの機能はVBにしかないのです。

※いや、VB以外にもあった気がしますけどメジャーな言語にはなかったと思います。

なお、次期バージョンのVBである仮称VB9では「型の推定」という機能が追加される予定のようです。

たとえば、次のコードを見てください。


Dim
X = 627
 

■リスト11

VB9ではこのような記述をした場合、XはInteger型となるそうです。これは今回説明した型に関する問題とは少し違う新しいスタンスですが、なんとなくOption Strict Offの方に近づいている感じがしませんか?

ここで終わったのではちょっとひどい気がしますので、最後に付け加えます。私はシビアな性能や機能を要求されているプログラムを作成する場合にはOption Strict Onを採用しています。やっぱり自動でチェックしてくれるのは便利ですからね。