[2018/05/24] 関数の戻り値がクラス(オブジェクト)の場合について (No.47)
[2018/05/22] 列挙体(Enum)の宣言と、メンバの値と名前の取得について(GetValues, GetName) (No.46)
[2018/05/21] 各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize) (No.45)
[2018/05/19] 文字列から数値型への変換(Parse , TryParse) (No.44)
[2018/05/18] 処理時間の計測(Stopwatchクラス) (No.43)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
関数の戻り値として、関数内でクラスのインスタンスを New で生成しその参照を戻す場合があります。 この場合、戻されたその参照の実体はどこにあるのでしょうか。これなのですがVB.NETシステムのヒープ領域のメモリ上に在る様です。 このメモリ上の参照しているのが New で生成された結果の値を保持する変数になります。
以下のソースにその例を示します。
frmRetval と名付けたフォーム上に1個のボタンを張り付けてあります。 このフォームクラス内に、戻り値テスト用のクラスとして clsRetTest を宣言しています。 このクラスは2個の内部変数とその内容を文字列で返すメソッド、及びコンストラクタの簡単な処理から構成します。
更にクラス clsRetTest を戻す関数として GetRetVal 関数を定義しています。戻り値テスト用
Public Class frmRetval '戻り値テスト用のクラス Private Class clsRetTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class 'クラスを戻す関数をテストする処理 Private Sub RetrunTest_Click(sender As Object, e As EventArgs) Handles RetrunTest.Click 'クラスを生成する関数コール Dim clsTest As clsRetTest = GetRetVal() '内部データの表示 MsgBox(clsTest.GetDataString) End Sub 'クラス(clsRetTest)を戻す関数 Private Function GetRetVal() As clsRetTest 'クラスの生成 Dim clsRet As New clsRetTest("123", 100) 'クラスを戻す Return clsRet End Function End Class
クラスを戻す関数をテストするのは、ボタンクリックイベントで行っています。 このイベントでは、クラスを生成する関数(GetRetVal)をコールし結果を clsTest 変数に入れています。 GetRetVal の中ではクラス clsRetTest がインスタンスがヒープ上に生成されその参照を戻り値として戻します。 イベント処理内ではその参照を使い、そのメソッドをコールすることができます。
このイベント処理内の変数 clsTest はこの処理を抜けると使用できなくなります。 (自動変数はそれ自身を含むブロックを超えては参照が出来ません。)
clsTest は使用できなくなるのは分かるのですが、先ほどのヒープ上の clsRetTest クラスの領域はどうなるのでしょうか。 少し心配になりますが、実はシステム側である時点が来た時にきれいに掃除をしてくれます。 (このことをガーベージコレクションと言うらしい。)
ボタンクリックが何度も押されると RetrunTest_Click が何度も実行されることになります。 この時はその度に、ヒープ上に別のインスタンスが生成され、そのインスタンスの参照が返されます。 そうすると使わないインスタンスがどんどん溜まっていくのですが、これもそのうちガーベージコレクションが行われて無くなる様です。 (ガーベージコレクションがいつ行われるかは神のみぞ知る的な感じな様ですが)
イベント処理内の clsTest がイベント処理が終わった後でも参照したい場合には、 その変数を以下のソースの様に、イベント処理内からフォームクラスの静的変数として関数の外で宣言すればできます。
クラス参照を静的変数で宣言
Public Class frmRetval '戻り値テスト用のクラス Private Class clsRetTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class 'クラス参照宣言 Dim clsTest As clsRetTest 'クラスを戻す関数をテストする処理 Private Sub RetrunTest_Click(sender As Object, e As EventArgs) Handles RetrunTest.Click 'クラスを生成する関数コール clsTest = GetRetVal() '内部データの表示 MsgBox(clsTest.GetDataString) End Sub 'クラス(clsRetTest)を戻す関数 Private Function GetRetVal() As clsRetTest 'クラスの生成 Dim clsRet As New clsRetTest("123", 100) 'クラスを戻す Return clsRet End Function 'クラスのメソッドのみをコールするイベント Private Sub DisplayClass_Click(sender As Object, e As EventArgs) Handles DisplayClass.Click '内部データの表示 MsgBox(clsTest.GetDataString) End Sub End Class
clsTest をフォームクラスの静的変数として宣言すれば、そこに設定されたヒープへの参照が残りますので、 それを使ってクラスの処理ができます。今回の例では、DisplayClass_Click がそれに当たります。
但し、このソースには問題が有ります。 RetrunTest_Click を行う前に DisplayClass_Click を行うと例外が発生してしまいます。 これは、clsTest が設定される前(値としては Nothing)にクラスのメソッドを使用するからです。 この問題を解消するには DisplayClass_Click 内で clsTest が Nothing では無いことを確認し 使用します。
尚、この clsTest ですが何時廃棄されるのかと言いますと、フォームクラスが廃棄される時点で行われ、 その後ガーベージコレクションが行われると思います。関連する記事
⇒関数の引数がクラスオブジェクトの場合の注意点について
PR -
区分データ等のソース上の既読性をアップする場合や、間違いを無くする場合に列挙体(Enum)を用います。
列挙体(Enum)の宣言は以下の様になります。 Enum 宣言の中のメンバーは値を指定しなくても、0からの値が割り振られます。 また、メンバーの値を"="イコールで定義すればその値となりますが、それ以降の値の定義されていないものは1加算された値が定義されます。
以下のソースでは、メンバー値を明示的に定義しない場合と、定義した場合の例を示します。列挙体(Enum)の宣言
'メンバ(定数)の値を定義しない場合のEnum Enum TestEnum1 Japan USA China Korea Other End Enum 'メンバ(定数)の値を定義する場合のEnum Enum TestEnum2 Japan = 100 USA China Korea = 1000 Other End Enum
TestEnum1 では「Japan」が「0」の値で、以下「USA」「China」「Korea」「Other」は順に「1」「2」「3」「4」の値となります。 また、TestEnum2 では「Japan」が「100」の値で、順に「101」「102」「1000」「1001」の値となります。
実際にプログラムで列挙体(Enum)のメンバー名とその値を列挙する方法を以下に示します。
以下のソースでは、 Enum.GetValues メソッドを用いることで、指定した列挙体に含まれている定数の値の配列が 返されます。 Enum.GetValues メソッドでは指定した値を持つ指定した列挙体にある定数の名前を取得します。
Enumのメンバ名と値を表示
'Enumのメンバ名と値を表示するテスト Private Sub EnumTest_Click(sender As Object, e As EventArgs) Handles EnumTest.Click 'TestEnum1のメンバの値を列挙 Dim Enum1Val As TestEnum1 Dim strMsg As String = "" For Each Enum1Val In [Enum].GetValues(GetType(TestEnum1)) 'メンバ名を取得する Dim strName As String = [Enum].GetName(GetType(TestEnum1), Enum1Val) 'メンバの値と名前を表示する strMsg &= String.Format("{0} = {1}", strName, CInt(Enum1Val)) & vbCrLf Next MsgBox("TestEnum1:" & vbCrLf & strMsg) 'TestEnum2のメンバの値を列挙 Dim Enum2Val As TestEnum1 strMsg = "" For Each Enum2Val In [Enum].GetValues(GetType(TestEnum2)) 'メンバ名を取得する Dim strName As String = [Enum].GetName(GetType(TestEnum2), Enum2Val) 'メンバの値と名前を表示する strMsg &= String.Format("{0} = {1}", strName, CInt(Enum2Val)) & vbCrLf Next MsgBox("TestEnum2:" & vbCrLf & strMsg) Enum2Val = TestEnum2.Other End Sub
尚、Enumには同じ値のものを別のメンバー名で定義することが可能ですが、 同じ値のメンバーがある場合は、注意が必要だと思います。 (プログラム上、必要になることもありますので、制限は必要無いとは思います)
列挙体(Enum)の宣言その2
'メンバ(定数)の値を定義する場合のEnum:2 Enum TestEnum2 Japan = 100 USA China Korea = 1000 Other = 1000 '同じ値で「Other」を宣言する End Enum
-
クラスや構造体などのオブジェクトのクローンコピーを作りたい場合があります。 各種のオブジェクトのデータを退避する時に、一括でクローンが作れれば便利なのですが、BinaryFormatter クラスを 使って、シリアライズとデシリアライズを行うことでできます。
以下のソースにその関数を記します。 BinaryFormatter の Serialize メソッドによりコピー元のオブジェクトをメモリストリームに展開します。 その後メモリストリームの内容を別のオブジェクトにデシリアライズを行います。ディープコピー関数
''' ----------------------------------------------------------------------- '''
''' オブジェクトのコピー ''' '''Type ''' <param name="objSrc">コピー元のオブジェクト</param> '''クローンオブジェクト ''' ----------------------------------------------------------------------- Function DeepCopy(Of T)(ByVal objSrc As T) As T 'クローンオブジェクト宣言 Dim objRet As T 'BinaryFormatterクラス Dim b As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 'バイト処理を行うメモリストリーム Dim ms As New System.IO.MemoryStream() Try 'シリアライズ(MemoryStreamにシリアライズ化して展開) b.Serialize(ms, objSrc) 'メモリ位置を初期化 ms.Position = 0 'デシリアライズ(MemoryStreamからデシリアライズ化してオブジェクトに戻す) objRet = CType(b.Deserialize(ms), T) Catch ex As Exception ' Finally ms.Close() End Try 'クローンを戻す Return objRet End Functionこの関数を使う例を以下に記します。
以下のソースでは、 DeepCopy 用のテストクラスとして clsCopyTest と、 テスト構造体として structCopyTest を宣言しています。
それぞれのクラス、構造体ともに <Serializable()> を指定しています。 これが無いとシリアライズ処理ができないので注意が必要です。
ディープコピー関数の使用例
'''
''' DeepCopy 用のテストクラス ''' <Serializable()> _ Private Class clsCopyTest Public _strTest As String Public _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class '''''' DeepCopy 用のテスト構造体 ''' <Serializable()> _ Private Class structCopyTest Private _strTest As String Private _intTest As Integer 'コンストラクタ Sub New(ByVal strTest As String, ByVal intTest As Integer) Me._strTest = strTest Me._intTest = intTest End Sub '内部データの表示用の文字列取得 Function GetDataString() As String Return "strTest=" & Me._strTest & vbCrLf & "intTest=" & Me._intTest.ToString End Function End Class Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'クラスでのテスト Dim clsT As New clsCopyTest("ABCD", 100) 'クラス生成 Dim clsCloneT As clsCopyTest = DeepCopy(clsT) 'クラスクローンコピー clsCloneT._strTest &= "..." 'クローンのデータを更新 clsCloneT._intTest *= 100 'クラスのメソッドで内容表示 MsgBox("Src :" & vbCrLf & clsT.GetDataString & vbCrLf & "Clone:" & vbCrLf & clsCloneT.GetDataString) '構造体でのテスト Dim strucT As New structCopyTest("EFGHI", 200) '構造体生成 Dim strucCloneT As structCopyTest = DeepCopy(strucT) '構造体クローンコピー MsgBox(strucCloneT.GetDataString) '構造体のメソッド End Subボタン処理内でクラスのテストと、構造体のテストを行っています。 クラスの方は、クローンコピーの値を少し変化を加えています。 その後、それぞれのクラス内の値を見てみると、両方の値が異なることが分かります。
関連する記事
⇒クラスのコピーについて(Object.MemberwiseClone)
-
文字列から数値型への変換はいろんな所で必要になりますが、例えばデータベースからのデータの取得で 結果を文字列にして読込んだ場合、これを数値にする場合などです。
数値変換には各データ型の Parse メソッドを利用して可能です。以下に例として Integer型 の変換関数を示します。文字列からInteger型への変換
'''
''' 文字列からInteger型にデータ変換 ''' ''' <param name="strVal">元の文字列</param> '''変換後データ Function StringToInt(ByVal strVal As String) As Integer Try Return Integer.Parse(strVal) '「Convert」クラスの関数を利用しても同じです 'Return Convert.ToInt32(strVal) Catch ex As Exception '例外発生の場合、「0」で返す Return 0 End Try End FunctionParse メソッドでの例外が発生した場合には全て0で返す様にしています。 データベースからのデータ取得で文字列から数値に変換する場合、ほとんど例外エラーは発生しないのでこの様にしています。 データベースの数値カラムからのデータで数値以外はあり得ないと思いますので。
但し、この関数ですが以下の様な使い方をすると非常に時間が掛かる様です。 (私のPC上では10秒以上の時間が掛かりました。)
文字列から数値型への変換の実行例1
Private Sub StrToInt_Click(sender As Object, e As EventArgs) Handles StrToInt.Click '変換が問題無い例 Dim strValue As String = "123456789" Dim intValue As Integer = 0 intValue = StringToInt(strValue) MsgBox("Integer変換:" & intValue.ToString) '時間計測を「Stopwatch」クラスで行う Dim stopwatch As New Stopwatch() stopwatch.Start() strValue = "ABCD" For i As Integer = 1 To 1000 '関数内でExceptionが発生するはず intValue = StringToInt(strValue) Next stopwatch.Stop() Dim str As String = "" str &= String.Format("ミリ秒単位の経過時間の合計 = {0}", stopwatch.ElapsedMilliseconds) MsgBox(str) End Sub
VB.NETの Exception 処理は非常に時間が掛かる処理の様で、今回の様な関数の呼び出しを多く行う場合には注意が必要です。 対策としては Exception 処理をなるべくさせないか、その他の方法を取る必要があります。
今回の対策としては Parse メソッドを TryParse メソッドに変更しています。
文字列からInteger型への変換・TryParse
'''
''' 文字列からInteger型にデータ変換その2 ''' ''' <param name="strVal">元の文字列</param> '''変換後データ Function StringToInt2(ByVal strVal As String) As Integer Try Dim intVal As Integer 'TryParseでInteger型に変換 Dim result As Boolean = Int32.TryParse(strVal, intVal) 'ParseでInteger型に変換 Return intVal Catch ex As Exception '例外発生の場合、「0」で返す Return 0 End Try End FunctionTryParse メソッドは変換結果を第2引数に返し、戻り値は True を返します。 尚、失敗した場合は第2引数に0を返し、戻り値は False を返します。
先ほどの使用例をこの関数を使う様に変更すれば、処理時間は格段に向上します。
とにかくネストが深い処理等で処理時間が異常に長い場合には、Exception 処理が絡んでいないかを疑ってみては如何でしょうか。
文字列から数値型への変換の実行例2
Private Sub StrToInt_Click(sender As Object, e As EventArgs) Handles StrToInt.Click '時間計測を「Stopwatch」クラスで行う Dim stopwatch As New Stopwatch() stopwatch.Start() strValue = "ABCD" For i As Integer = 1 To 1000 '呼び出し関数を変更 intValue = StringToInt2(strValue) Next stopwatch.Stop() Dim str As String = "" str &= String.Format("ミリ秒単位の経過時間の合計 = {0}", stopwatch.ElapsedMilliseconds) MsgBox(str) End Sub
関連する記事
⇒オブジェクト型から数値型への変換(TryParse)
⇒文字列定数(改行、タブ、バックスペース等)について
⇒文字列変換関数(StrConv)で変換が途中で切れる
⇒文字列変換関数(StrConv)の使い方
-
処理時間の短縮を図る場合に、処理にどのくらいの時間が掛かっているのかを計測する場合があります。
基本的には秒単位での時間計測で問題無い場合は、計測対象の処理の前で時刻を Now() で取得し、 処理終了後に再度 Now() で取得した時刻の差を求めることで計測ができます。処理時間の計測その1
Private Sub TimeMeasure1_Click(sender As Object, e As EventArgs) Handles TimeMeasure1.Click '開始時刻退避 Dim start As DateTime = Now '各種の処理の代わりとしての待ち時間(10秒) System.Threading.Thread.Sleep(10000) '処理時間を計算(現在時刻-開始時刻) Dim timespan As TimeSpan = Now - start MsgBox(String.Format("処理時間 = {0}秒", timespan.TotalSeconds)) End Sub
更に、高精度な時間を計測したい場合には、 Stopwatch クラスを用います。
以下のソースでは、 Stopwatch クラス生成後、計測対象処理の直前で Start メソッドで計測開始し、 計測対象処理の直後で Stop メソッドで計測終了します。
処理時間の計測その2
Private Sub TimeMeasure2_Click(sender As Object, e As EventArgs) Handles TimeMeasure2.Click '時間計測を「Stopwatch」クラスで行う Dim stopwatch As New Stopwatch() '計測を開始 stopwatch.Start() '各種の処理の代わりとしての待ち時間(10秒) System.Threading.Thread.Sleep(10000) '計測を終了 stopwatch.Stop() Dim str As String = "" str &= String.Format("経過時間の合計 = {0}", stopwatch.Elapsed) & vbCrLf str &= String.Format("ミリ秒単位の経過時間の合計 = {0}", stopwatch.ElapsedMilliseconds) MsgBox(str) End Sub
関連する記事
⇒スレッドタイマの使い方 :[System.Threading.Timer,Delegate,Invoke]
⇒スレッドタイマの使い方(イベント処理が時間が掛かる場合):[System.Threading.Timer,Delegate,Invoke]
⇒引数があるスレッドの実行について(パラメータ付きスレッド)
⇒スレッドを停止させる方法について