トップ回答者
DimとNewの効率的な使い方がわかりません。

質問
-
こんにちわ。
VB初心者のなつです。
よろしくお願い致します。
とても基本的な話で申し訳ないのですが、
DimとNewの使い方で最近どうしていいかわからなくなることがあります。
説明することが苦手なので簡単なサンプルパターンコードを文末に添付致します。
例えば下記のサンプルで
パターン③は「blnFlg」が「True」の場合、「ClassB」のインスタンスを生成しなくて良いという利点があると思います。
(その分処理が早くなる?メモリを節約できる?)
しかし私にはパターン②とパターン③の違いがよくわかりません。
パターン②では「Dim」で宣言だけして、その条件にあった時だけ「New」をするということなんですが・・・・
「Dim」で「宣言」をするということと、
「New」をして「インスタンスを生成する」ということが
頭の中で整理しきれてません。
どうかアドバイスよろしくお願い致します。
Class A
Private Function a() As Boolean'パターン①
Dim blnFlg, blnRet As Boolean
Dim objB As New BIf blnFlg = True Then
blnRet = objB.b()
If blnRet = False Then
Return blnRet
End If
End If'パターン②
Dim blnFlg, blnRet As Boolean
Dim objB As BIf blnFlg = True Then
objB = New B
blnRet = objB.b()
If blnRet = False Then
Return blnRet
End If
End If'パターン③
Dim blnFlg, blnRet As BooleanIf blnFlg = True Then
Dim objB As New B
blnRet = objB.b()
If blnRet = False Then
Return blnRet
End If
End If'パターン④
Dim blnFlg, blnRet As BooleanIf blnFlg = True Then
Dim objB As B
objB = New B
blnRet = objB.b()
If blnRet = False Then
Return blnRet
End If
End IfEnd Function
End ClassClass B
Friend Function b() As Boolean
Return False
End Function
End Class
回答
-
外池と申します。おそらく、よく理解されているんだと思いますよ? パターンの2、3、そして、4も、natuさんの仰るとおり「ほとんど」変わりません。
Dimは、変数を宣言するためのもの、Newは、オブジェクトのインスタンスを作成するためのもの、というのが教科書的な説明の仕方ですが、もう少し詳しくすると・・・、
--------
「オブジェクト」というものは、その機能を発揮するために、いろんなデータを内蔵していますよね? 今回示して頂いているClass Bの場合はひとつの関数しか入っていませんが、もっと発展させれば、いろんなデータを保持しておくことが必要になります。与えられたものですが、Formを想像していただければ、相当量のデータを内蔵していることがイメージできると思います。
オブジェクトのインスタンスを作成する、ということは、そのような内蔵するデータのためにメモリを確保する、ということとほぼ同義だと考えれば良いかと思います。これが、Newで行われることです。
一方、Dim(正確には参照型の変数を宣言するDim)は、メモリ上の、オブジェクトのために確保された位置を記録するための変数を宣言するわけです。
--------
「Dim objB as ClassB」は、メモリ上の位置を記録するための変数だけが宣言されており、で、中身はNothing(まだ、どこの位置も指していない)です。
で、「objB = New ClassB」とNewを使った代入文を書けば、具体的にオブジェクトが作られて(メモリが確保されて)、その位置がobjBに記録されます。
この2行を1行にまとめてかく書くと「Dim objB As New Class B」となります。
--------
objBは位置を記録する変数に過ぎないので、次々とNewしてオブジェクトの別個のインスタンスを作って、その位置をとっかえひっかえobjBに代入することも可能です。
逆に・・・、このようなとっかえひっかえをしてしまうと、いくつものインスタンスの位置が、どの変数にも記録されていなくて「忘れられてしまう」ことになることは・・・、わかりますでしょうか? そうすると、メモリが無駄に占有されたままになってしまいそうですが、.Net Frameworkの場合は、このようなどの変数も覚えてくれていないオブジェクトのインスタンスは不要になったものと自動的に判定してくれて、メモリの占有は解放されるようになっています。これが、ガベージ・コレクションです。
参考になれば幸いです。
-
外池です。natuさんが「効率的」という言葉を使って質問されているので、おそらく、パフォーマンスに関することもご質問の趣旨に含まれているのかな? と思います。んで、少々、私の考えを(一部、私自身もよくわかってないところがあるので、便乗質問)
まずは、ヒープ・メモリーの使い方に関して
trapemiyaさんが、オブジェクトの例として配列を掲げられましたが、私の場合、大きな配列を使うことがよくあります。(要素数が数万とか、数十万とか) 当たり前ですが、大量のメモリーを食うオブジェクトとして、次々にNewでインスタンスを作ると、あっという間にメモリーが足りなくなります。先ほど書きましたが、一度Newで作ったインスタンスでも、どの変数からも忘れられてしまったものは不要と判断されてガベージコレクションでメモリーから消してもらえます。メモリーが足りない傾向が続けば、自動的にガベージコレクションの頻度も多くなります。
このような仕組みは、「ちゃんと動きます」という点では便利なのですが、「ちゃんと動く」けれども「すごく遅く」なります。(ガベージコレクションが発生しなくても、メモリーのスワップが発生して遅くなりますし、ガベージコレクションも時間のかかる操作です。)
当たり前ですが、必要の無いインスタンスをNewで作ってしまうことは無駄です。で、一度Newで作ったインスタンスは、可能ならば、別のインスタンスを作らずに使いまわした方が効率的です。巨大な配列も、新しいものが必要になったとき、Newで作り直しても良いのですが、既存のインスタンスを使って個々の要素の値を改めてセットしなおしてやって使いまわす方が、メモリーの消費量も少なくてすむし、速いことも多いです。
次に、スタック・メモリーの使い方に関して
If分のThenの中だけ、あるいはElseの中だけに現れる変数を、メソッドの先頭でDimで宣言しておくか、それとも、ThenやElseの中だけでDimで宣言するか、これは、効率にはほとんど影響しないと思います。どちらかと言えば、プログラムが読みやすいかどうか、保守性の問題だと思います。
そんなわけで、Dimは、まぁ、プログラムを書く作業で必要があれば、どこでも宣言して良いと思いますが、Newでオブジェクトのインスタンスを作る操作は、特にオブジェクトが巨大な場合は、よく考えないといけないと思います。
一方で・・・、(以下、私もよくわかっていないところですが・・・)
GDI+の描画なんかでは、penオブジェクトのインスタンスを、数百、数千とnewで作って、disposeして、というようなプログラムを書いてしまったりしています。penオブジェクトはそれほど大きなものではないのであまり目立ちませんが、本来は避けるべきことなんでしょうね? penオブジェクトのプロパティーを変更しながら、あちこちの描画シーンで使いまわした方が良いのでしょうか?
-
外池です。
お示しいただいたプログラムでは、「objB=New B」がForループの中にありますので、何回も実行されますよね? Newが実行される度に、新しいインスタンスができてしまいます。この場合だと11個のインスタンスができてしまうことになります。
objBが「あるインスタンス」を参照している状態で、
objB=New Bをすると、
「あるインスタンス」への参照は無くなる(「あるインスタンス」は忘れられてしまう。)
そして、objBは、「新しいインスタンス」を参照するようになる。
そんなわけで、このプログラムだと・・・、一番最初を除いて、Forループの2周目からは、「あるインスタンス」を忘れて「新しいインスタンス」を参照することが繰り返されます。
さらに、そんなわけで、Newによる代入のときに、「あるインスタンス」を忘れる動作も含まれているので、実のところ、objB=Nothing(パターン1)をする必要は無いです。
さらにさらに、Function a()を抜ければ、objB変数は使えなくなり、必然的に、最後にobjBが参照していたインスタンスも忘れられてしまいます。ですので、objB=Nothing(パターン2)も不要。
忘れられたインスタンスたちは、その後、適当な時に(プログラマーは気にしなくてよい)システムのガベージ・コレクションで掃除されます。
--------------
Class Bをもう少し手の込んだものにすると、面白いかと思います。
Code SnippetClass B
'インスタンスの数を保管する クラスに一つだけある変数
'複数の異なったインスタンスから共通の値として読み取れる。
Private Shared IDcount As Integer = 0'これは、個々のインスタンスに個別に存在する変数
Private mID As IntegerPublic Sub New()
'Newのときに、自動的に呼び出される。
'新しくつくられるインスタンスの準備作業を
'プログラムするのに使える。
IDcount += 1
mID = IDcount
End SubFriend Function b(ByVal intCnt As Integer) As Boolean
Console.WriteLine(mID)
Console.WriteLine(CStr(intCnt))
Return True
End Function
End Class
すべての返信
-
外池と申します。おそらく、よく理解されているんだと思いますよ? パターンの2、3、そして、4も、natuさんの仰るとおり「ほとんど」変わりません。
Dimは、変数を宣言するためのもの、Newは、オブジェクトのインスタンスを作成するためのもの、というのが教科書的な説明の仕方ですが、もう少し詳しくすると・・・、
--------
「オブジェクト」というものは、その機能を発揮するために、いろんなデータを内蔵していますよね? 今回示して頂いているClass Bの場合はひとつの関数しか入っていませんが、もっと発展させれば、いろんなデータを保持しておくことが必要になります。与えられたものですが、Formを想像していただければ、相当量のデータを内蔵していることがイメージできると思います。
オブジェクトのインスタンスを作成する、ということは、そのような内蔵するデータのためにメモリを確保する、ということとほぼ同義だと考えれば良いかと思います。これが、Newで行われることです。
一方、Dim(正確には参照型の変数を宣言するDim)は、メモリ上の、オブジェクトのために確保された位置を記録するための変数を宣言するわけです。
--------
「Dim objB as ClassB」は、メモリ上の位置を記録するための変数だけが宣言されており、で、中身はNothing(まだ、どこの位置も指していない)です。
で、「objB = New ClassB」とNewを使った代入文を書けば、具体的にオブジェクトが作られて(メモリが確保されて)、その位置がobjBに記録されます。
この2行を1行にまとめてかく書くと「Dim objB As New Class B」となります。
--------
objBは位置を記録する変数に過ぎないので、次々とNewしてオブジェクトの別個のインスタンスを作って、その位置をとっかえひっかえobjBに代入することも可能です。
逆に・・・、このようなとっかえひっかえをしてしまうと、いくつものインスタンスの位置が、どの変数にも記録されていなくて「忘れられてしまう」ことになることは・・・、わかりますでしょうか? そうすると、メモリが無駄に占有されたままになってしまいそうですが、.Net Frameworkの場合は、このようなどの変数も覚えてくれていないオブジェクトのインスタンスは不要になったものと自動的に判定してくれて、メモリの占有は解放されるようになっています。これが、ガベージ・コレクションです。
参考になれば幸いです。
-
ついでにこちらもご覧になっておくと、理解が深まるのではないかと思います。
3-3-1 VB .NET の配列
http://www.microsoft.com/japan/msdn/net/vbnetref/vbnetref3-3.aspx
の
◆スタックとヒープ
(追記)その下の◆ガベージコレクションも読まれると良いと思います。
(追記2)
--- ◆スタックとヒープ から引用 開始 -------------------------------------------------------------------------------
値型と参照型によって、このスタックとヒープの使われ方が違います。値型や参照型のローカル変数自体はスタック領域にあります。参照型の場合、スタックに存在するのは参照情報を保持する変数だけです。それに対して、参照型の変数で参照する配列の実体はヒープに確保されます。
--- ◆スタックとヒープ から引用 終了 -------------------------------------------------------------------------------
「スタックに存在するのは参照情報を保持する変数」がDimで宣言するところ、「参照型の変数で参照する配列の実体」がnewで作成するインスタンス。
-
外池です。natuさんが「効率的」という言葉を使って質問されているので、おそらく、パフォーマンスに関することもご質問の趣旨に含まれているのかな? と思います。んで、少々、私の考えを(一部、私自身もよくわかってないところがあるので、便乗質問)
まずは、ヒープ・メモリーの使い方に関して
trapemiyaさんが、オブジェクトの例として配列を掲げられましたが、私の場合、大きな配列を使うことがよくあります。(要素数が数万とか、数十万とか) 当たり前ですが、大量のメモリーを食うオブジェクトとして、次々にNewでインスタンスを作ると、あっという間にメモリーが足りなくなります。先ほど書きましたが、一度Newで作ったインスタンスでも、どの変数からも忘れられてしまったものは不要と判断されてガベージコレクションでメモリーから消してもらえます。メモリーが足りない傾向が続けば、自動的にガベージコレクションの頻度も多くなります。
このような仕組みは、「ちゃんと動きます」という点では便利なのですが、「ちゃんと動く」けれども「すごく遅く」なります。(ガベージコレクションが発生しなくても、メモリーのスワップが発生して遅くなりますし、ガベージコレクションも時間のかかる操作です。)
当たり前ですが、必要の無いインスタンスをNewで作ってしまうことは無駄です。で、一度Newで作ったインスタンスは、可能ならば、別のインスタンスを作らずに使いまわした方が効率的です。巨大な配列も、新しいものが必要になったとき、Newで作り直しても良いのですが、既存のインスタンスを使って個々の要素の値を改めてセットしなおしてやって使いまわす方が、メモリーの消費量も少なくてすむし、速いことも多いです。
次に、スタック・メモリーの使い方に関して
If分のThenの中だけ、あるいはElseの中だけに現れる変数を、メソッドの先頭でDimで宣言しておくか、それとも、ThenやElseの中だけでDimで宣言するか、これは、効率にはほとんど影響しないと思います。どちらかと言えば、プログラムが読みやすいかどうか、保守性の問題だと思います。
そんなわけで、Dimは、まぁ、プログラムを書く作業で必要があれば、どこでも宣言して良いと思いますが、Newでオブジェクトのインスタンスを作る操作は、特にオブジェクトが巨大な場合は、よく考えないといけないと思います。
一方で・・・、(以下、私もよくわかっていないところですが・・・)
GDI+の描画なんかでは、penオブジェクトのインスタンスを、数百、数千とnewで作って、disposeして、というようなプログラムを書いてしまったりしています。penオブジェクトはそれほど大きなものではないのであまり目立ちませんが、本来は避けるべきことなんでしょうね? penオブジェクトのプロパティーを変更しながら、あちこちの描画シーンで使いまわした方が良いのでしょうか?
-
外池さん、trapemiyaさん、
回答ありがとうございました。
とても参考になりました。
と同時に自分の無知さがわかりました↓
これからがんばります。
お二人のお話を聞いていてさらに気になるキーワードが出てきました。
「インスタンスの参照の破棄」と「インスタンスの再利用」です。
(ガベージ・コレクションの動くタイミングも・・・・)
例えば下記のサンプルで
同じ変数に対して何度も「インスタンスの生成」を行っています。
この場合の「インスタンスの実体」は同じものを「再利用?(表現がおかしいですね;)」しているのですか?
それとも新しくどんどん作成していくのでしょうか?
またこの場合、「インスタンスの実体」から参照をさせなくするタイミングはパターン①②ではどちらが好ましいですか?
(私はパターン②が好ましいかと思います。)
Class A
Private Function a() As BooleanDim objB As B
Dim blnRet As Boolean
Dim intCnt As IntegerFor intCnt = 0 To 10 Step 1
objB = New B 'インスタンスの生成
blnRet = objB.b(intCnt)objB = Nothing 'パターン①
If blnRet = False Then
Return blnRet
End If
Next
objB = Nothing 'パターン②End Function
End ClassClass B
Friend Function b(ByVal intCnt As Integer) As Boolean
Console.WriteLine(CStr(intCnt))
Return True
End Function
End Class -
外池です。
お示しいただいたプログラムでは、「objB=New B」がForループの中にありますので、何回も実行されますよね? Newが実行される度に、新しいインスタンスができてしまいます。この場合だと11個のインスタンスができてしまうことになります。
objBが「あるインスタンス」を参照している状態で、
objB=New Bをすると、
「あるインスタンス」への参照は無くなる(「あるインスタンス」は忘れられてしまう。)
そして、objBは、「新しいインスタンス」を参照するようになる。
そんなわけで、このプログラムだと・・・、一番最初を除いて、Forループの2周目からは、「あるインスタンス」を忘れて「新しいインスタンス」を参照することが繰り返されます。
さらに、そんなわけで、Newによる代入のときに、「あるインスタンス」を忘れる動作も含まれているので、実のところ、objB=Nothing(パターン1)をする必要は無いです。
さらにさらに、Function a()を抜ければ、objB変数は使えなくなり、必然的に、最後にobjBが参照していたインスタンスも忘れられてしまいます。ですので、objB=Nothing(パターン2)も不要。
忘れられたインスタンスたちは、その後、適当な時に(プログラマーは気にしなくてよい)システムのガベージ・コレクションで掃除されます。
--------------
Class Bをもう少し手の込んだものにすると、面白いかと思います。
Code SnippetClass B
'インスタンスの数を保管する クラスに一つだけある変数
'複数の異なったインスタンスから共通の値として読み取れる。
Private Shared IDcount As Integer = 0'これは、個々のインスタンスに個別に存在する変数
Private mID As IntegerPublic Sub New()
'Newのときに、自動的に呼び出される。
'新しくつくられるインスタンスの準備作業を
'プログラムするのに使える。
IDcount += 1
mID = IDcount
End SubFriend Function b(ByVal intCnt As Integer) As Boolean
Console.WriteLine(mID)
Console.WriteLine(CStr(intCnt))
Return True
End Function
End Class