[2019/10/09] コレクション「List」の使い方について (No.130)
[2019/10/01] オブジェクト型から数値型への変換(TryParse) (No.126)
[2019/09/05] 金額計算(会計処理など)は Decimal 型で、科学計算には Double 型 を使用する (No.123)
[2019/09/03] 配列の範囲指定によるコピー(Array.Copy, Skip, Take) (No.121)
[2019/08/30] クラスのコピーについて(Object.MemberwiseClone) (No.120)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
コレクションクラスの中に List がありますが、以前の記事の配列と似た処理が可能です。 配列では少し面倒だった領域を拡張などが無くなる分、使い勝手は良い様に思います。
それでは List の使い方について順を追って説明したいと思います。■簡単な List の宣言と使い方
以下のソースを見て下さい。
処理の前半は、以前の配列を利用した場合の合計処理を示しています。 配列の各要素に値を設定する部分は、ベタに記述していますが、 合計を求めるところで指標を使ったループ処理で行っています。
処理の後半で List を生成し Add メソッドで新しく要素を追加します。 合計を求める処理では配列の様に Item プロパティで要素を取得しています。
尚、 List の要素を指標で取得する場合は、指標は「0」番目から始まります。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '=== 配列宣言 === Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer = 0 For i = 1 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) '=== Listの生成 === Dim list As New List(Of Integer) '要素に値を追加 list.Add(1) list.Add(10) list.Add(100) list.Add(1000) list.Add(10000) nSum = 0 '合計計算 For i = 0 To list.Count - 1 Console.WriteLine("{0}番目のListの要素:{1}", i, list.Item(i)) nSum = nSum + list.Item(i) Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub
実行結果がコンソールには以下様に表示されます。
配列を利用した場合の合計処理:11111 0番目のListの要素:1 1番目のListの要素:10 2番目のListの要素:100 3番目のListの要素:1000 4番目のListの要素:10000 List を利用した場合の合計処理:11111
■List の繰り返し処理は For Each で行う
上記のソースでは List の要素を Item プロパティで取得しましたが、 For Each 文を使えば List の要素を順次取得できます。 (こちらの方が通常よく使います。)
そこで上記のソースを以下の様に変更し実行してみます。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '=== Listの生成 === Dim list As New List(Of Integer) '要素に値を追加 list.Add(1) list.Add(10) list.Add(100) list.Add(1000) list.Add(10000) Dim nSum As Integer = 0 '合計計算 For Each nVal As Integer In list nSum = nSum + nVal Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub
結果は最初の場合と同様になります。
List を利用した場合の合計処理:11111
■List の初期化
List の生成とで一緒に初期化ができます。 生成の宣言の後に、From で繋ぎ {}(中括弧) で要素を ,(カンマ) で区切って記述します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000} '指標宣言 Dim i As Integer = 0 '合計計算 Dim nSum As Integer = 0 '合計計算 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 nSum = nSum + nVal Next Console.WriteLine("List を利用した場合の合計処理:{0}", nSum) End Sub
実行結果は以下の通りで、データの順番は記述した順となっています。
0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 List を利用した場合の合計処理:11111
このソース合計計算の For ループで「5」を直値(リテラル)で記述しています。 この場合は「5」であることが明らかなので問題は無いのですが、 配列の最終の指標が何か分からない時には UBound 等を用います。 以下に項目を新しくして説明します。
■List の定義と各メソッド等について
List クラスは以下の様に定義されています。
Public Class List(Of T) 型パラメーター: T:リスト内の要素の型。 概要: インデックスを使用してアクセスできる、厳密に型指定されたオブジェクトのリストを表します。 リストの検索、並べ替え、および操作のためのメソッドを提供します。
「リスト内の要素の型」にはいろんなものが設定可能で、データ型、オブジェクト型、コントロールオブジェクト等が指定できます。 また List クラスには以下の様なメソッドがあり、要素に対する各種の処理が可能です。
メソッド or
プロパティ書式 説明 Add Add(item As T)
item:List(Of T) の末尾に追加するオブジェクトList(Of T) の末尾にオブジェクトを追加します AddRange AddRange(collection As IEnumerable(Of T))
collection:List(Of T) の末尾に要素が追加されるコレクション指定したコレクションの要素を List(Of T) の末尾に追加します Clear Clear() 指定List(Of T) からすべての要素を削除します Count Count As Integer (これはプロパティ) List(Of T) に実際に格納されている要素の数を取得します IndexOf IndexOf(item As T) As Integer (プロパティ)
item:List(Of T) 内で検索するオブジェクトList(Of T) 全体内で item が見つかった場合は、
最初に見つかった位置の 0 から始まるインデックス
それ以外の場合は –1IndexOf IndexOf(item As T, index As Integer) As Integer
(プロパティ)
item:List(Of T) 内で検索するオブジェクト
index:検索の開始位置で 0 から始まるインデックスList(Of T) 全体内で item が見つかった場合は、
最初に見つかった位置の 0 から始まるインデックス
それ以外の場合は –1Insert Insert(index As Integer, item As T)
index:挿入する位置の 0 から始まるインデックス
item:挿入するオブジェクトList(Of T) 内の指定したインデックスの位置に要素を挿入 Item Item(index As Integer) As T (プロパティ)
index:取得,設定する要素の 0 から始まるインデックス
例外:ArgumentOutOfRangeException
index が 0 未満,または List(Of T).Count 以上指定したインデックスにある要素 Remove Remove(item As T) As Boolean
item:List(Of T) から削除するオブジェクト
戻り値:
item が正常に削除された場合は true,それ以外は falseList(Of T) 内で最初に見つかった特定のオブジェクトを削除します RemoveAt RemoveAt(index As Integer)
index:削除する要素の、0 から始まるインデックス
例外:ArgumentOutOfRangeException
index が 0 未満,または List(Of T).Count 以上List(Of T) の指定したインデックスにある要素を削除します RemoveRange RemoveRange(index As Integer, count As Integer)
index:削除する要素の範囲の開始位置
( 0 から始まるインデックス)
count:削除する要素の数
例外:ArgumentOutOfRangeException
index が 0 未満 または count が 0 未満
例外:ArgumentException
index, count が List(Of T) 内の要素の有効範囲外List(Of T) から要素の範囲を削除します
上記のメソッド等を順を追って説明します。- AddRange コレクションの要素を末尾に追加
- Clear と Count について
- IndexOf コレクション要素のインデックスの検索
- Insert 指定したインデックスの位置に要素を挿入
- Remove RemoveAt RemoveRange 要素の削除
■AddRange コレクションの要素を末尾に追加
2個の List を生成し、最初の List に2番目の List を追加します。 この時 (Of T) のデータ型が同じで無い場合はエラーが発生します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '指標宣言 Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000} Dim listAdd As New List(Of Integer) From {2, 20, 200} ' AddRange Console.WriteLine("=== AddRange ===") list.AddRange(listAdd) For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next ' 異なる型のList Dim listAdd2 As New List(Of Long) From {3, 30, 300} Try ' エラーが必ず発生 list.AddRange(listAdd2) Catch ex As Exception Console.WriteLine("AddRangeエラー:{0}", ex.Message) End Try End Sub
実行結果は以下の通りです。最初の AddRange で List の末尾に追加されたことが分かります。 また、異なるデータ型の場合にはエラーが発生しています。
=== AddRange === 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:2 6番目に取得したListの要素:20 7番目に取得したListの要素:200 AddRangeエラー:型 'System.Collections.Generic.List`1[System.Int64]' のオブジェクトを型 'System.Collections.Generic.IEnumerable`1[System.Int32]' にキャストできません。
■Clear と Count について
先ず List を生成しその一覧を Item プロパティで取得し、その後 Clear で List の要素を全て削除します。
Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click '指標宣言 Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100} ' Listの一覧 Console.WriteLine("=== Listの一覧 ===") For i = 0 To list.Count - 1 Console.WriteLine("{0}番目のListの要素:{1}", i, list.Item(i)) Next Console.WriteLine("=== Clear ===") ' Clear list.Clear() Console.WriteLine("Listの要素個数:{0}", list.Count) i = 0 For Each nVal As Integer In list ' ForEachでの一覧は処理されない Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next End Sub
実行結果は以下の通りです。Count で List の件数分の一覧処理が行われることが分かります。 また Clear 後は件数が「0」になり For Each での処理が行われません。
=== Listの一覧 === 0番目のListの要素:1 1番目のListの要素:10 2番目のListの要素:100 === Clear === Listの要素個数:0
■IndexOf コレクション要素のインデックスの検索
先ず List を生成しその要素の値指定でインデックスを IndexOf 関数で取得します。
その後、検索開始インデックスを指定する IndexOf での例を以下に示します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) 0 1 2 3 4 5 6 7 Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 2, 20, 200} ' IndexOf(item As T) Console.WriteLine("=== IndexOf(item As T) ===") i = list.IndexOf(1000) Console.WriteLine("itemが「{0}」のインデックス:{1}", 1000, i) i = list.IndexOf(1500) Console.WriteLine("itemが「{0}」のインデックス:{1}", 1500, i) ' IndexOf(item As T, index As Integer) Console.WriteLine("=== IndexOf(item As T, index As Integer) ===") i = list.IndexOf(20, 4) Console.WriteLine("itemが「{0}」をインデックス「{1}」から検索した結果のインデックス:{2}", 20, 4, i) i = list.IndexOf(1000, 4) Console.WriteLine("itemが「{0}」をインデックス「{1}」から検索した結果のインデックス:{2}", 1000, 4, i) End Sub
実行結果は以下の通りです。
要素の値指定のみの IndexOf では先頭から検索する為「1000」の値の場合はインデックス「3」が返り、 「1500」の値の場合は List の要素に存在しないので「-1」が返ります。
検索開始インデックスを指定する IndexOf では開始インデックスを「4」とし、検索要素値「20」の場合は「6」が返り、 検索要素値「1000」の場合はインデックスを「4」以降には値が存在しないので「-1」が返ります。=== IndexOf(item As T) === itemが「1000」のインデックス:3 itemが「1500」のインデックス:-1 === IndexOf(item As T, index As Integer) === itemが「20」をインデックス「4」から検索した結果のインデックス:6 itemが「1000」をインデックス「4」から検索した結果のインデックス:-1
■Insert 指定したインデックスの位置に要素を挿入
先ず List を生成しインデックス「5」の位置に「300」を挿入します。
その後、存在しないインデックス「10」の位置に挿入処理を行いエラーが発生することを確認します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 2, 20, 200} ' Insert(index As Integer, item As T) Console.WriteLine("=== Insert(index As Integer, item As T) ===") list.Insert(5, 300) 'インデックス「5」の位置に「300」を挿入 i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.Insert(10, 900) 'インデックス「10」の位置に「900」を挿入 Catch ex As Exception Console.WriteLine("Insertエラー:{0}", ex.Message) End Try End Sub
実行結果は以下の通りです。
インデックス「5」の位置に「300」が挿入され、それ以降は順次繰り下がったことがわかります。
また、インデックス「10」の位置での挿入処理はエラーが返されています。=== Insert(index As Integer, item As T) === 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:300 6番目に取得したListの要素:2 7番目に取得したListの要素:20 8番目に取得したListの要素:200 Insertエラー:インデックスは一覧の範囲内になければなりません。 パラメーター名:index
■Remove RemoveAt RemoveRange 要素の削除
先ず List を生成し Remove で値が「300」の要素を削除します。2回行うことで異なる結果が返ってくるはずです。
その後 RemoveAt でインデックス「5」の位置を削除処理を行い一覧確認します。 さらに存在しないインデックス「10」の位置を削除処理し、エラー発生を確認します。
その後 RemoveRange でインデックス「3」の位置から「2」個の要素を削除処理を行い一覧確認します。 さらにインデックス「3」の位置から存在しない個数の「5」で削除処理し、エラー発生を確認します。Private Sub btnList_Click(sender As Object, e As EventArgs) Handles btnList.Click Dim i As Integer = 0 ' Listの生成(初期化) Dim list As New List(Of Integer) From {1, 10, 100, 1000, 10000, 300, 2, 20, 200} Dim blnRet As Boolean ' Remove(item As T) Console.WriteLine("=== Remove(item As T) ===") blnRet = list.Remove(300) Console.WriteLine("itemが「{0}」削除:{1}", 300, blnRet) blnRet = list.Remove(300) Console.WriteLine("itemが「{0}」削除:{1}", 300, blnRet) ' RemoveAt(index As Integer) Console.WriteLine("=== RemoveAt(index As Integer) ===") list.RemoveAt(5) Console.WriteLine("インデックス「{0}」の要素を削除", 5) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.RemoveAt(10) 'インデックス「10」の位置の要素削除 Catch ex As Exception Console.WriteLine("RemoveAtエラー:{0}", ex.Message) End Try ' RemoveRange(index As Integer, count As Integer) Console.WriteLine("=== RemoveRange(index As Integer, count As Integer) ===") list.RemoveRange(3, 2) Console.WriteLine("インデックス「{0}」から「{1}」個の要素を削除", 3, 2) i = 0 For Each nVal As Integer In list Console.WriteLine("{0}番目に取得したListの要素:{1}", i, nVal) i += 1 '指標+1 Next Try ' エラーが必ず発生 list.RemoveRange(3, 5) Catch ex As Exception Console.WriteLine("RemoveRangeエラー:{0}", ex.Message) End Try End Sub
実行結果は以下の通りです。
=== Remove(item As T) === itemが「300」削除:True itemが「300」削除:False === RemoveAt(index As Integer) === インデックス「5」の要素を削除 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:1000 4番目に取得したListの要素:10000 5番目に取得したListの要素:20 6番目に取得したListの要素:200 RemoveAtエラー:インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。 パラメーター名:index === RemoveRange(index As Integer, count As Integer) === インデックス「5」から「2」個の要素を削除 0番目に取得したListの要素:1 1番目に取得したListの要素:10 2番目に取得したListの要素:100 3番目に取得したListの要素:20 4番目に取得したListの要素:200 RemoveRangeエラー:配列のオフセットおよび長さが範囲を超えているか、カウンターがソース コレクションのインデックスから最後までの要素の数より大きい値です。
関連する記事
⇒コレクション「List」と配列の相互変換について
⇒コレクション「Dictionary」の使い方について
⇒コレクション「Dictionary」から配列及び List への変換について
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)
PR -
文字列から数値型への変換は以下のページにありますが、今回はオブジェクト型から数値への変換を記します。
⇒文字列から数値型への変換(parse - tryparse)
Integer型に変換するために Integer.TryParse メソッドを使いますが、引数としては文字列なので 先ずはオブジェクト型から文字列型への変換関数を宣言します。 ソースは以下の様になります。(Objectデータが「Nothing」「DBNULL」の場合には空文字列を返します。)オブジェクト型からString型への変換
'''
''' Object型からString型にデータ変換 ''' ''' <param>元のObject</param> '''String型に変換後の値 '''String型に変換できない場合は「""」を返す Function ObjToStr(ByVal objSrc As Object) As String Dim strRet As String Try If objSrc Is Nothing Then 'Objectデータが「Nothing」の場合 strRet = "" ElseIf IsDBNull(objSrc) Then 'Objectデータが「DBNULL」の場合 strRet = "" Else '文字列に変換 strRet = CStr(objSrc) End If Catch ex As Exception 'エラーの場合 strRet = "" End Try '変換値を返す Return strRet End Functionこのオブジェクト型から文字列型への変換関数を利用して、 オブジェクト型からInteger型への変換関数を宣言します。
オブジェクト型からInteger型への変換
'''
''' Object型からInteger型にデータ変換 ''' ''' <param>元のObject</param> '''Integer型に変換後の値 '''Integer型に変換できない場合は「0」を返す Function ObjToInt(ByVal objSrc As Object) As Integer Dim intRet As Integer Try 'String型に変換 Dim strSrc As String = ObjToStr(objSrc) 'Integer型に変換 If Integer.TryParse(strSrc, intRet) = False Then '変換が不可の場合 intRet = 0 End If Catch ex As Exception 'エラーの場合 intRet = 0 End Try '変換値を返す Return intRet End Function
更にオブジェクト型からDecimal型への変換は以下の様になります。 (その他のDouble型とかも同様に宣言できます)
オブジェクト型からDecimal型への変換
'''
''' Object型からDecimal型にデータ変換 ''' ''' <param>元のObject</param> '''Decimal型に変換後の値 '''Decimal型に変換できない場合は「0」を返す Function ObjToDec(ByVal objSrc As Object) As Decimal Dim decRet As Decimal Try 'String型に変換 Dim strSrc As String = ObjToStr(objSrc) 'Decimal型に変換 If Decimal.TryParse(strSrc, decRet) = False Then '変換が不可の場合 decRet = 0 End If Catch ex As Exception 'エラーの場合 decRet = 0 End Try '変換値を返す Return decRet End Function関連する記事
⇒文字列から数値型への変換(parse - tryparse)
-
通常プログラムを組んでいて計算処理を行う場合にはそこまで気にすることは無いのですが、 小数点以下の計算で誤差が生じない様にするため金額計算では Decimal 型変数を使い、 科学計算的な場合には Double 型変数を使用する様にしています。
また、ループの指標等、少ない桁の整数の場合には Integer 型変数を使い、 桁数が多い整数の計算では Long 型変数を使います。
尚、各データ型の内容ですが以下の様になっています。■各データ型について
データ型 型説明 サイズ 有効桁数 値の範囲 Integer 整数型 4バイト -2147483648 ~ 2147483647 Long 長整数型 8バイト -9223372036854775808 ~ 9223372036854775807 Single 単精度浮動小数点型 4バイト 7桁 -3.40282347E+38 ~ 3.40282347E+38 Double 倍精度浮動小数点型 8バイト 15桁 -1.7976931348623157E+308 ~ 1.7976931348623157E+308 Decimal 10進型 16バイト 28桁 -7.9228162514264337594E+28 ~ 7.9228162514264337594E+28 ■Single 型と Double 型について
浮動小数点型は内部的に符号部、指数部、仮数部に分けてデータを持っているそうです。 Single 型は符号部(1ビット)、指数部(8ビット)、仮数部(23ビット) で Double 型は符号部(1ビット)、指数部(11ビット)、仮数部(52ビット) で構成されているらしいです。 (詳しくは IEEE 754 の説明にあたって下さい。)
Single 型 Double 型の計算を行わせてどのくらいの精度があるのかを見てみます。
以下のソースでは小数「0.1」を10回加算して結果を有効桁20桁までで表示させています。 「0.1」と言う数値は2進数にすると「0.00011001100110011…」の循環小数になることはよく知られています。 これを10回加算して結果が数学的に「1」になるでしょうか?Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '単精度数値変数 Dim sngVal As Single = 0 '倍精度数値変数 Dim dblVal As Double = 0 'それぞれ「0.1」を10回加算 For i As Integer = 1 To 10 sngVal += 0.1F 'リテラルで「F」指定は単精度数値 dblVal += 0.1D 'リテラルで「D」指定は倍精度数値 Next '結果が「1」になることを期待? Console.WriteLine("Single Data = {0}", sngVal.ToString("G20")) Console.WriteLine("Double Data = {0}", dblVal.ToString("G20")) End Sub
実行結果がコンソールには以下様に表示されます。 やはり「1」にはなりません。 リテラルで加算される「0.1」は循環小数なので内部的にはどこかで丸めが発生しているからです。
Single Data = 1.00000012 Double Data = 0.99999999999999989
Single 型では有効桁数が少ないので、小数以下4桁どうしの乗算を行うと小数以下8桁まで必要になりますが、 小数以下8桁以降は信頼できない値になります。これでは科学計算には向かないので、 Double 型を使うことになります。
■Decimal 型について
上記の処理を Decimal 型に変えて処理します。
Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '10新型数値変数 Dim decVal As Decimal = 0 '「0.1」を10回加算 For i As Integer = 1 To 10 decVal += 0.1D 'リテラルで「D」指定は10新型数値 Next '結果が「1」になることを期待? Console.WriteLine("Decimal Data = {0}", decVal.ToString("G20")) decVal = 0.1D Console.WriteLine("Decimal Data = {0}", decVal.ToString("G30")) End Sub
実行結果がコンソールには以下様に表示されます。 確かにこの程度の小数以下の精度では問題無い様です。
Decimal Data = 1 Decimal Data = 0.1
Decimal 型はデータサイズも大きく内部での計算は他の型よりも時間が掛かりますが、 会計処理などでは小数点以下の精度に正確性が求められますので、この型を使います。
私が組むプログラムは OA 系の処理が多いので数値は全て Decimal 型を使っています。 まれに、工場のライン監視などで計測データが必要なときは Double 型も使いますが、 そこまで速度が要求されなければ Decimal 型を使います。
■リテラル(直値)宣言の注意
プログラム上で直接値(リテラル)を宣言する場合に注意が必要です。 値を正しく宣言しないと期せずして異なるデータ型で処理されてることがあります。
以下のソースは小数点付きで宣言した場合と、小数点無し(整数)で宣言した場合に、数値の後ろにデータ型を指示する文字を 付加する場合としない場合の例を示します。Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '各種データ型を入れるため「Object」型 Dim objTest As Object = Nothing 'Decimal型のリテラル objTest = 0.1 Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Single型のリテラル objTest = 0.1F Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Integer型のリテラル objTest = 12345 Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Long型のリテラル objTest = 12345L Console.WriteLine("Type = {0}", objTest.GetType.ToString) End Sub
実行結果がコンソールには以下様に表示されます。
Type = System.Double Type = System.Single Type = System.Int32 Type = System.Int64
■各データ型のリテラル指定について
データ型 型説明 リテラル指定 Integer 整数型 I Long 長整数型 L Single 単精度浮動小数点型 F Double 倍精度浮動小数点型 R Decimal 10進型 D 関連する記事
⇒指定した精度の桁数に数値を切り上げ :[Math.Ceiling,Math.Floor]
⇒文字列から数値型への変換(parse - tryparse)
⇒オブジェクト型から数値型への変換(TryParse)
-
配列から範囲を指定して要素を抽出し、新しい配列を作成する方法について記します。
それでは以下の方法を説明したいと思います。■新規配列を生成し Array.Copy でコピー
Array.Copy の引数の説明は以下の通りです。
Array.Copy(sourceArray As System.Array, sourceIndex As Integer, destinationArray As System.Array, destinationIndex As Integer, length As Integer) ・sourceArray :コピー元の配列データ。 ・sourceIndex :コピー操作の開始位置となる sourceArray 内のインデックスを表す 32 ビット整数。 ・destinationArray:コピー先の配列データ。 ・destinationIndex:格納を開始する destinationArray のインデックスを表す 32 ビット整数。 ・length :コピーする要素の数を表す 32 ビット整数。
以下のソースを見て下さい。 最初にコピー元配列「arr」を宣言し、コピー先の配列「arr2」をコピー数で領域確保しています。 今回の処理ではコピー元の位置を「6」として3個の要素をコピーしています。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー元の位置 Dim iPos As Integer = 6 'コピー先で3個の要素 Dim nLen As Integer = 3 Dim arr2(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arr2, 0, nLen) 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub
実行結果がコンソールには以下様に表示されます。確かにコピーされている様です。
arr2(0):6 arr2(1):7 arr2(2):8
この処理を関数に以下の様にまとめてみました。'''
''' 配列切出し ''' ''' <param name="arr">切出し元の配列</param> ''' <param name="iPos">切出し位置</param> ''' <param name="nLen">要素数</param> '''切出した配列を返す Function SliceArray(ByVal arr() As Integer, ByVal iPos As Integer, ByVal nLen As Integer) As Integer() '指定位置が配列の大きさを超えた? If iPos >= arr.Length Then Return {} End If 'コピー最終位置 Dim iLast As Integer = iPos + nLen - 1 '指定位置からの指定個数が配列の大きさを超えた? If iLast >= arr.Length Then '個数の調整 nLen = arr.Length - iPos End If '返す配列の生成 Dim arrRet(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arrRet, 0, nLen) '配列を返す Return arrRet End Function■配列を Enumerable.Skip、Enumerable.Take メソッドで処理
System.Linq.Enumerable の Skip メソッドを使うことで、配列内の一連の要素から指定位置から後ろのデータを全て取得します。 更に System.Linq.Enumerable の Take メソッドで先頭から指定個数の要素を切出します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー '「arr」の先頭から6個の要素をスキップしてそれ以降を配列生成 Dim arr2() As Integer = System.Linq.Enumerable.Skip(arr, 6).ToArray '「arr2」の先頭から3個の要素のみで配列生成 arr2 = System.Linq.Enumerable.Take(arr2, 3).ToArray '以下の様に連結しても同様!! 'Dim arr2() As Integer = arr.Skip(6).Take(3).ToArray 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub
関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
-
今回はクラスのコピーについて考えてみますが、以下の順で説明したいと思います。
- クラス変数のコピー
- クラスの実体コピー(Object.MemberwiseClone)
- クラスの実体コピー・参照型を含む(Object.MemberwiseClone)
- クラスの実体コピー・ディープコピー(Serializable)
■クラス変数のコピー
早速クラスのテストを以下のソースで行ってみます。
テスト用クラスとして Integer 型と String 型の2個のパブリック変数を持ち、 コンストラクタで値を設定するものとします。'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラス変数のコピー cTst2 = cTst1 '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=200, strData=BBB
クラス変数のコピーを行った後で、[cTst1]のデータを変更したつもりでも、[cTst2]のデータとして表示すると同じになってます。 これは、クラスの変数にはクラス生成されたデータ領域の参照が設定されるだけで、 クラス変数のコピーではその参照値(ポインタ)がコピーされるだけです。
結果、[cTst1]も[cTst2]も参照が指し示す実体は同じものとなり、 [cTst1]でデータにアクセスしても、[cTst2]でデータにアクセスしたことと同じになります。
どうしてもコピーしたい場合は、[cTst2]を新しいクラスで生成して、クラスの中身をそれぞれコピーすることはできます。 以下のソースでその例を示します。(しかし、これでは変数が多い場合は手間ですし、カッコよくありませんが...)
'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'デフォルトのコンストラクタ Sub New() End Sub 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラスのコピー cTst2 = New cTest cTst2.intData = cTst1.intData cTst2.strData = cTst1.strData '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。当然ですが、[cTst1]のデータと[cTst2]のデータは異なるものが表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
本当はクラス自体が配列のコピーの Clone メソッドの様なものを持てればすっきりするのですが。
■クラスの実体コピー(Object.MemberwiseClone)
クラスにコピーメソッドを実装する為にソースを変更します。 先ずクラスにクローン作成をサポートするClone メソッドを定義し Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを返してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone Implements ICloneable 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub 'コピーメソッド Public Function Clone() As cTestWithClone Return MemberwiseClone() '現在の System.Object の簡易コピー作成 End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA") Dim cTst2 As cTestWithClone 'クラスのクローンコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
これでほぼ目的は達成されるのですが、クラス内に参照型のデータが存在する場合、参照のみがコピーされるため注意が必要です。 (このあたりが、「簡易コピー」といわれる所以です。)
■クラスの実体コピー・参照型を含む場合(Object.MemberwiseClone)
クラスに参照型データとして Integer 型の配列を定義します。 例のコピーメソッドの実装では Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを作成し、 作成できない参照型を個別にクローン生成してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub 'コピーメソッド Public Function Clone() As cTestWithClone '先ず簡易コピー作成 Dim cTest As cTestWithClone = MemberwiseClone() '参照型データが設定済みならばクローン作製 If MyClass.arr IsNot Nothing Then cTest.arr = MyClass.arr.Clone End If 'コピーを返す Return cTest End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA", {100, 200, 300}) Dim cTst2 As cTestWithClone 'クラスのコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBBB, arr(1)=100, arr(2)=0, arr(0)=300 cTst2 : nData=100, strData=AAAA, arr(1)=100, arr(2)=200, arr(0)=300
これで参照型データのコピーが出来ることが分かりました。 尚、参照型データが複数ある場合は、全てにおいて処理を繰り返す必要があります。
■クラスの実体コピー・ディープコピー(Serializable)
以下の記事にもありますが、クラスも1個のオブジェクトなのでクラスの実体をシリアル化して、別の実体にシリアル化を戻せば、 クラスのコピーができることになります。
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
以下のソースに上記の記事内の関数を利用してクラスのコピーを行います。
クラスの宣言の先頭で <Serializable()> と記述し、シリアル化ができることを宣言しないと、関数でコピーができません。'テストクラス(シリアル化可能) <Serializable()> Class cTest 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA", {10, 20, 30}) Dim cTst2 As cTest 'クラスのコピー cTst2 = DeepCopy(cTst1) '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB, arr(1)=10, arr(2)=0, arr(0)=30 cTst2 : nData=100, strData=AAAA, arr(1)=10, arr(2)=20, arr(0)=30
関連する記事
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)