どのように多くの要素が配列変数は、ASPを収容することができます
Dr. GUI .NET #6: .NET Framework での配列
.NET Framework での配列
June 11, 2002
日本語版最終更新日 2002 年 9 月 30 日
目次
前回のコラムと今回のコラム
配列と 1 次元配列の概要
値型の配列と参照型の配列
配列クラス
多次元配列
配列または配列要素の変換 (配列の共分散)
配列の配列 (または "不規則行" の配列)
試してみましょう!
まとめと次回予告
前回のコラムと今回のコラム
前回のコラム では、 .NET Framework の文字列について説明しました。 今回は、.NET Framework の配列について説明します。
配列と 1 次元配列の概要
.NET Framework の配列は、他のプログラミング システムの配列とよく似ています。 配列は、順序付けられた値のセットを含むデータ構造です。 すべての要素は、同じ型である必要があります。 ただし、参照型の場合、要素が要素型または要素型から派生された型のオブジェクトを参照できることに注意してください。 この極端な例として、Object の配列があります。 すべての型が Object から派生されているので、 このような配列の要素は任意の型を参照できます。
配列名、および整数式のインデックスを使用して、値にアクセスします。 インデックスは、C# では角かっこ、Microsoft® Visual Basic® .NET ではかっこで囲んで設定します。 たとえば、次のようになります。
[C#] double[] a = {0.1, 0.2, 0.3, 0.4, 0.5}; // "0.3" を指定します。: 最初のインデックスは、1 ではなく 0 です。 StringBuilder sb = new StringBuilder(a[2].ToString()); int i = 2; a[i] = a[i] + a[i - 1] / 4; // 0.3 を 0.35 に置き換えます。 sb.AppendFormat(", now {0}\n", a[2].ToString()); [Visual Basic .NET] Dim a() as Double = {0.1, 0.2, 0.3, 0.4, 0.5} ' "0.3" を指定します。: 最初のインデックスは、1 ではなく 0 です。 Dim sb as New StringBuilder(a(2).ToString()) Dim i as Integer = 2 a(i) = a(i) + a(i - 1) / 4 ' 0.3 を 0.35 に置き換えます。 sb.AppendFormat(", now {0}\n", a(2).ToString())
上記の例では、1 行目の左側の部分で、"a" という名前の配列参照を作成します。 その配列参照の型は、 double [] または Double()、 つまり double または Double の 1 次元配列です。 配列のランクまたは次元の数は型の一部ですが、 配列のサイズは、その型の一部ではないことに注意してください (これは、C と C++ では異なります)。 C# では、 C や C++ のように
と記述するのではなく、 Double
a[]
= { ... };
と記述する必要があることにも注意してください。 C# の配列宣言では、配列の角かっこが変数ではなく、型に付きます。 Visual Basic .NET の場合は、かっこが型ではなく、変数に付きます。 紛らわしいでしょう? Dr. GUI もそう思います... Double
[] a
= { ... };
配列参照は、次元の数 (またはランク) と要素型が同じである限り、 任意のサイズの配列を参照できます。 (もちろん、配列参照は、実際の配列を参照するのではなく、null になることもできます。)
1 行目の右側の部分では、 5 つの要素を持つ配列を指すように配列参照を設定しています。 これらの要素のインデックスは、1 から 5 ではなく、0 から 4 です。 ("従来の" Visual Basic では、 "Option Base" ステートメントを使用して、 最初のインデックスを 1 に設定できました。 今はできません。) つまり、すべての要素を出力するループを次のように記述します。
[C#] for (int i1 = 0; i1 <= 4; i1++) sb.AppendFormat("a[{1}] = {0}\n", a[i1], i1); [Visual Basic .NET] Dim i1 as Integer For i1 = 0 To 4 sb.AppendFormat("a({1}) = {0}\n", a(i1), i1) Next sb.Replace("\n", Environment.NewLine)
C、C++、または C# で (まだ) プログラムを作成したことがない人のために説明すると、 上記の C# のループでは、i1 を 0 に設定して、 AppendFormat メソッドの呼び出しを 5 回実行し、 実行するたびに i1 をインクリメントしています。 これは、Visual Basic .NET の For i1 = 0 To 4 とまったく同様のものです。 そのため、i1 の値は 0、1、2、3、4 と変化します (i1 が 5 になると、i1 <= 4 という条件に当てはまらないためです)。
名医がパラメータを好きな順序に設定するために、 AppendFormat メソッドの呼び出し内で書式指定子を切り替える機能を利用したことに注意してください。
Visual Basic .NET のコードでは、 コードを終了する (および StringBuilder から文字列を返す) 前に、 "\n" サブ文字列を CR/LF に変換します。
1 つずれエラー : Visual Basic .NET の重要な相違点!
Visual Basic .NET と C# の 1 つの重要な相違点は、 固定の要素数を指定したときに割り当てられる配列要素の数にあります。
たとえば、 C# で int [] a = new int[5];
と記述すると、 a[0] から a[4] までの 5 つの要素を確保します。
Visual Basic .NET では、 Dim a() as Integer = New Integer(5)
と記述すると、 a[0] から a[5] までの 6 つの要素を確保します。
つまり、Visual Basic .NET は自動的にいつのまにか各次元のサイズに 1 を加算します。 Visual Basic は、レガシの理由でこの操作を行います。 さらに、これは古い Visual Basic のプログラムの Visual Basic .NET への移行を少し簡単にします。
そのため、明確なサイズを使用して上記の配列を割り当てる場合、 C# では 5 を、Visual Basic .NET では 4 をサイズとして使用すると、 0 から 4 の番号の付いた要素を持つ同じ配列を確保できます。
この相違点が原因で問題が発生します。 次元が変数のとき、特にその変数が異なる言語で記述されたルーチン間で渡される場合は、 とりわけ解決が難しい問題になります。 結果として、配列のサイズが予想とは異なり、 それが原因で解決が難しい 1 つずれエラーと NULL ポインタ参照の例外が発生することがあります。
注意してください! 特に、言語を混在して使用する場合は! そして、あなたが Visual Basic .NET のプログラマの場合は、 .NET Framework のメソッドに配列を渡すとき、このことに注意してください!
配列の境界を確認する
配列の上限または下限を超えるインデックスを指定すると、 IndexOutOfRangeException が生成されます。 .NET Framework では、 配列のインデックスを確認して、 それらが境界内に収まるようにする必要があります。 特に大量の配列操作を実行するアプリケーションでは、 これがパフォーマンスの問題になることがあります。 このようなアプリケーションでは、 C# の安全ではないコードの機能を使用して、 C または C++ 形式のポインタやメモリの管理を実行します。 (ただし、十分注意してください!) Visual Basic .NET にはこれに相当するものがありません。 そのため、.NET Framework で言語にまたがって簡単に呼び出しができることはすばらしいことです。
クエリは何ですか配列は参照型である
.NET Framework の配列は参照型です。 そのため、 この配列は null または Nothing、 あるいは double または Double の配列への参照のいずれかを含むことができます。 多くのプログラマは配列参照に null や Nothing が含まれると予想しないので、 配列参照を null や Nothing に設定する場合には注意してください。 代わりに、これを空の配列 (要素を持たない配列) に設定できます。
[C#] double[] a; // a は割り当てられていません (null を保持します)。 a = null; // この設定を行わない場合は、上記の説明を参照してください。 a = new double[] { }; // 多くの場合、空の配列は null より優れています。 [Visual Basic .NET] Dim a() as Double ' a は割り当てられていません (Nothing を保持します)。 a = Nothing ' この設定を行わない場合は、上記の説明を参照してください。 Erase a ' 上のコードと同じです。簡単に a を Nothing に設定します。 a = New Double() { } ' 多くの場合、空の配列は Nothing より優れています。
(Visual Basic の Erase ステートメントは、 配列参照を Nothing に設定するだけです。)
変数を宣言した後に、配列に割り当てること (および再割り当て) もできます。 たとえば、上記の例の最初の部分を次のように記述できます。
[C#] double[] a; // a は割り当てられていません。 a = new double[] {0.1, 0.2, 0.3, 0.4, 0.5}; // 再割り当てできます 。 // double[] a = new double[]{0.1, 0.2, 0.3, 0.4, 0.5} と同様です。 [Visual Basic .NET] Dim a() as Double ' a は割り当てられていません。 a = New Double(){0.1, 0.2, 0.3, 0.4, 0.5} ' 再割り当てできます。 ' Dim a as double() new Double() {0.1, 0.2, 0.3, 0.4, 0.5} と同様です。 ReDim Preserve a(8) ' 配列を 8 に拡張し、元の値を保存します。
Visual Basic .NET には、 ReDim ステートメントがあります。 このステートメントは、ある形式では、単純な参照の再割り当てと同じことを行います。 ただし、別の形式では、Preserve というキーワードを使用することによって、 古い配列の内容を新しい配列に自動的にコピーし、 配列の最終的な次元のサイズを変更できるようになります。 (上記のように、コピーする要素が少ない場合、余分な要素を 0 で初期化します。)
すべての参照型に当てはまることですが、 ある配列を別の配列に割り当てると、 両方とも同じ配列を参照します。 配列のコピーが必要な場合は、 次に示すようにその配列をクローン化する必要があります。
[C#] StringBuilder sb = new StringBuilder(); double[] b = a; // b と a は同じ配列を参照します。 b[2] = 7.7; sb.AppendFormat("a[2] is {0}, b[2] is {1}\n", a[2], b[2]); // 両方とも 7.7 です。 double[] c = (double [])a.Clone(); // 別の配列になります。 c[2] = 3.8; sb.AppendFormat("a[2] is {0}, c[2] is {1}\n", a[2], c[2]); // 同じではなくなります。 [Visual Basic .NET] Dim sb as New StringBuilder Dim b() as Double = a ' b と a は同じ配列を参照します。 b(2) = 7.7 sb.AppendFormat("a(2) is {0}, b(2) is {1}\n", a(2), b(2)) ' 両方とも 7.7 です。 Dim c() as Double = CType(a.Clone(), Double()) ' 別の配列になります。 c(2) = 3.8 sb.AppendFormat("a(2) is {0}, c(2) is {1}\n", a(2), c(2)) ' 同じではなくなります。 sb.Replace("\n", Environment.NewLine) ' \n を変更します。
値型の配列と参照型の配列
すべての型の配列では、配列変数自体が配列への参照です。 ただし、その配列には何が含まれるのでしょうか?
配列参照が参照する配列には、 配列内の実際のデータの他に、 いくつかの興味深い情報が含まれています。 たとえば、ランク (次元数) や、配列の各次元の上限と下限が含まれています。 (.NET Framework は、 下限が 0 以外の配列を許可しますが、 そのような配列は CLS に準拠していないので、 ほとんどの言語では直接宣言できません。 最も例外的な場合を除いて使用しないでください。)
しかし、ちょっと待ってください! データはどのようになっているのでしょうか?
真相は次のとおりです。 配列が Int32、Double、Char または独自の値型のいずれか (たとえば、struct や Struct というキーワードを使用して宣言されたもの) など、 ある値型の配列の場合、 実際には、その配列にデータが含まれます。
たとえば、ここで示した配列は次のようになります。
図 1. 値型 Double の配列
すべての値型は、同様のレイアウトになります。 (a と b は両方とも同じ配列を参照するように設定したので、 同じ配列を参照することに注意してください。)
ところが、文字列などの参照型の配列の場合、 その配列には、文字列そのものではなく、 オブジェクト (この場合は文字列) への参照が含まれます。 たとえば、文字列の配列を次のように設定します。
[C#] string[] sa = {"zero", "one", "two", "three", "four"}; [Visual Basic .NET] Dim sa() as String = {"zero", "one", "two", "three", "four"}
配列は、メモリ内で次のようになります。
図 2. 参照型、文字列の配列
参照型のすべての配列は、同様のレイアウトになります。
スカラー (配列ではない) 参照型の変数があるときと同様に、 参照型が間接参照のレベルを追加することに注意してください。 文字列への参照が配列に格納され、 文字列自体は別の場所に格納されます。 コンパイラが逆参照を正しく行うと、 次のようなコードは正しく機能します。
[C#] StringBuilder sb = new StringBuilder(); for (int i = 0; i <= 4; i++) sb.AppendFormat("sa[{1}] is {0}\n", sa[i], i); [Visual Basic .NET] Dim sb as New StringBuilder Dim i as Integer For i = 0 To 4 sb.AppendFormat("sa({1}) is {0}\n", sa(i), i) Next sb.Replace("\n", Environment.NewLine)
配列クラス
実際の配列オブジェクトは特別な配列型です。 すべての配列型は、基本クラス System.Array から派生します。 もちろん、System.Array は System.Object から派生します。 これは、すべての配列がオブジェクトなので、 System.Array に対して (もちろん、System.Object に対しても) 実行できることは、 すべての配列に対して実行できることを意味します。
どのようにデータベーススキーマを作成するObject に対して実行できることについては、 最近のコラムで説明しました。 実際に重要なことは、 すべての配列の派生元である System.Array クラスを調べることによって、 任意の配列に対して何を実行できるか考えることです。
まず、System.Array が ICloneable、IList、ICollection、 および IEnumerable といういくつかのインターフェイスを実装していることに気付きます。
これは、これらのインターフェイスのいずれかに任意の配列をキャストしたり、 これらのインターフェイスのいずれかによってオブジェクトを取得するメソッドに任意の配列を渡すことができることを意味します。
配列のクローン化
配列が ICloneable を実装するということは、 Clone を呼び出して、 配列をクローン化できることを意味します。 たとえば、b = a
と記述して先に示した配列を割り当てる代わりに、 b = a.Clone()
と記述して配列をクローン化すると、 単に参照だけでなく、配列全体のコピーを作成することになります。
ただし、Clone メソッドは、配列が参照するオブジェクトをコピーしません。 そのため、string[] sb = (string [])sa.Clone();
または Dim sb() as String = CType(sa.Clone(), string)
と記述すると、 配列の文字列への参照 (先に示した図の中央の列) をコピーしますが、 文字列自体 (右側の列) はコピーしません。 これは、"浅いコピー" と呼ばれます。 (Clone は Object を返すので、 キャストが必要なことに注意してください。) この浅いコピーの動作は、2 つの配列が同じ文字列セットを指すようにしたい場合に役に立ちます。 たとえば、一方の文字列参照の配列を逆方向に並べ替え、 もう一方の配列を順方向に並び替えるときなどに役に立ちます。
"深いコピー" を実行する場合は、配列を作成し、各文字列をコピーするループを記述しします。
[C#] public static string CloneStringArray() { string[] sa1 = {"rei", "ichi", "ni", "san", "shi"}; // sa1 が一次元配列であることを確認します。 if ((sa1 is System.Array) && (sa1.Rank == 1)) { // 同じサイズの配列を作成します。 string[] sa2 = new string[sa1.Length]; for (int i = sa2.GetLowerBound(0); i <= sa2.GetUpperBound(0); i++) { // String.Clone は使用できません。以下を参照してください。 sa2[i] = String.Copy(sa1[i]); } sa2[0] = "zero"; sa2[4] = "yon"; // PrintRefArrayToString は後で定義します... return PrintRefArrayToString(sa1, "sa1") + "\n" + PrintRefArrayToString(sa2, "sa2"); } else return "配列をクローン化できませんでした"; } [Visual Basic .NET] Function CloneStringArray() As String Dim sa1() As String = {"rei", "ichi", "ni", "san", "shi"} ' sa1 が一次元配列であることを確認します。 If IsArray(sa1) AndAlso sa1.Rank = 1 Then ' 同じサイズの配列を作成します。 Dim sa2(sa1.Length - 1) As String ' 1 つずれることに注意します。 Dim i As Integer For i = sa2.GetLowerBound(0) To sa2.GetUpperBound(0) ' String.Clone は使用できません。以下を参照してください。 sa2(i) = String.Copy(sa1(i)) Next sa2(0) = "zero" sa2(4) = "yon" ' PrintRefArrayToString は後で定義します... Return PrintRefArrayToString(sa1, "sa1") & Chr(10) & _ PrintRefArrayToString(sa2, "sa2") Else Return "配列をクローン化できませんでした" End If End Function
ここで、名医がさらに 3 つの Array メソッドを使用していることに気が付きます。 Rank は次元数を、 GetLowerBound は指定した次元の下限を、 そして、GetUpperBound は指定した次元の上限を返します。 Visual Basic .NET には、すべての配列で使用できる LBound および RBound という関数もあります。 さらに、Visual Basic .NET では、IsArray により、 特定のオブジェクトが配列かどうか見分けることができます。 C# では、sa1 is System.Array
という構文を使用して、同じことができます。 既にこれが配列であるとわかっているので、ここではやり過ぎです (実際には、コンパイラが警告します)。 しかし、Dr. GUI は、このことを説明したかったのです。
また、C# の && 演算子および Visual Basic .NET の AndAlso 演算子に気付きます。 これらの演算子は、"ショートサーキット" 演算子として機能します。 演算子の左側の式が間違っている場合、右側は評価さえされません。 この場合、sa1
が配列ではない可能性があるので、 Rank プロパティを読み取ろうとする必要はありません! ついでながら、"or" に相当するショートサーキット演算子は、 C# では ||、Visual Basic .NET では OrElse (Dr. GUI はこの名前が大好きです!) になります。
IEnumerable と foreach または For Each
System.Array は IEnumerable も実装します。 つまり、クラスを標準的な方法で列挙できるということです。 IEnumerable には、GetEnumerator というメソッドが 1 つだけあります。 このメソッドは、IEnumerator を実装する別の列挙子オブジェクトを作成して返します。 (このオブジェクトは、同じコレクションで複数の列挙子を持つことができるように別のオブジェクトです。)
IEnumerator インターフェイスには、 Current というプロパティと MoveNext と Reset という 2 つのメソッドがあります。 Current プロパティは、列挙子が参照する現在のオブジェクトを返します。 MoveNext メソッドは、列挙子をコレクション内の次の項目に移動します。 Reset メソッドは、コレクションの先頭の直前に列挙子を再設定します。 (最初の項目に進めるには、 新たに作成した列挙子で MoveNext を呼び出す必要があることに注意してください。)
C# および Visual Basic .NET には、 このすべての機能を使用する foreach というステートメントがあります (Visual Basic .NET では、For Each と呼ばれています)。 たとえば、配列全体を出力するには、次のコードを実行します。
[C#] StringBuilder sb = new StringBuilder(); foreach (string s in sa) sb.AppendFormat("{0}***", s); [Visual Basic .NET] Dim sb as New StringBuilder Dim s as String For Each s In sa sb.AppendFormat("{0}***", s) Next
このコードは、sa が多次元配列の場合でも機能します! ただし、ほとんどの .NET コンパイラは、 配列が 0 を下限とする 1 次元配列である foreach または For Each で特殊なコードを実行します。 コンパイラは、列挙子を使って配列にアクセスするのではなく、 (ランタイムによってインライン化できる) 特別に最適化されたコードを生成して配列にアクセスします。 相違点については、ILDASM 内のコードを調べてください。
ピン番号を入力する方法既に示した深いコピーの例では、 foreach および For Each を使用できません。 これは、配列の要素を列挙している間はその要素を変更できないためです。 foreach は "読み取り専用" です。 そのため、通常のループを使用しました。 ただし、配列の内容を変更せず、 インデックスを必要としない場合は、 foreach を使用すべきです。 foreach の方が単純なので、 コンパイラとランタイムはより優れた最適化を行うことができます。
その他のインターフェイス
System.Array は、ICollection と IList も実装します。 ICollection は、 Count、IsReadOnly、IsSynchronized、 および SyncRoot の各プロパティを提供するインターフェイスです (余談ですが、このインターフェイスは IEnumerable から継承されています)。 これらは、コレクションへのアクセスの同期をとるための同期可能なオブジェクトを返す SyncRoot を除いて、 動作が明確です。 このインターフェイスは、コレクションを配列にコピーするための CopyTo メソッドも提供します。
IList は、 ICollection から継承されており、 C# のインデクサに対応する Item プロパティ、 およびリストを扱うための一連のメソッドである Add、Insert、Remove、RemoveAt、Contains、 (値のインデックスを返す) IndexOf、および Clear を追加しています。 これらの動作はかなり明確です。 詳細については、System.Array のドキュメントを参照してください。 また、IsFixedSize および IsReadOnly というメソッドも追加します。 配列が IList を実装することにより、 配列、および同じように IList を実装するリンク リストなどのその他のデータ構造を処理できるようになります。 IList インターフェイスのメンバは明示的な実装により実装されることに注意してください。 つまり、これらのメソッドを呼び出すために、 配列参照を IList 型にキャストする必要があります。 配列では、直接このインターフェイスのメンバを呼び出すことはできません。 さらに、Insert、Remove および RemoveAt は機能せず、 NotSupportedException をスローすることにも注意してください。 これは、配列が固定サイズであるためです。 したがって、要素の挿入および削除はできません。
System.Array のその他のメンバ
System.Array のメンバの一覧は長く、 ほとんどの関数の名前はその動作内容を示しています。 そのため、名医は、インターフェイスを実装しないメンバの名前だけを一覧して、 明確ではないメンバについて説明します。
まず、BinarySearch、Clear、 (配列の一部を別の配列にコピーする) Copy、 (下限が 0 ではない配列など、任意の型の配列をプログラムで作成できる) CreateInstance、 IndexOf と LastIndexOf、Reverse、Sort といった、 多数の静的メソッドがあります。
既に、すべてのプロパティとほとんどのインスタンス メソッドについては説明しましたが、 他にも、GetLength、CopyTo、Initialize、GetValue、 および SetValue といったものがあります。
よく見られるメンバについてはある程度詳しく説明したので、 残りのメンバについては、 「Array メンバ」ドキュメントを自由に調べてください。
一般に、独自の .NET Framework 言語を開発している場合を除いて、 Array.Initialize を使用することはありません。
多次元配列
.NET Framework は真の多次元配列もサポートします。 (ある言語と言っても、Dr. GUI が名前を付けているわけではありませんが、 名前が "J" で始まる言語は多次元配列をまったくサポートしておらず、 効率の悪い配列の配列だけをサポートしています。 .NET Runtime は両方をサポートします。 選択できることはすばらしいことです。)
繰り返しますが、各次元のサイズは配列の型の一部ではありません。 そのため、int の 2 次元配列は、 "int [ , ]
" という型を持ち、 string の 3 次元配列は、 "string [ , , ]
" という型を持ちます。
多次元配列の宣言および初期化の構文は、 1 次元配列の構文とよく似ています。 主な違いは、初期化子で、各次元を中かっこで囲む必要があることです。
[C#] int [,,] x = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; [Visual Basic .NET] Dim x(,,) as Integer = _ { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, _ { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }
上記の例では、x は 2 x 3 x 4 の int または Integer の配列です。 多次元配列の各行、各列などは、同じ次元内の他の行、列などと同じサイズにする必要があります。
foreach または For Each ステートメントは、 多次元配列のすべての要素で反復処理を行い、 最も右側の次元の値から値を増加していきます。
[C#] StringBuilder sb = new StringBuilder(); foreach (int i in x) sb.AppendFormat("{0} ", i); // 1 2 3 4 5 6 ... 24 [Visual Basic .NET] Dim sb as New StringBuilder() Dim i as Integer For Each i in x sb.AppendFormat("{0} ", i) ' 1 2 3 4 5 6 ... 24 Next
ただし、多次元配列を処理するには、1 セットの入れ子ループを使用するのが最も一般的な方法です。
[C#] sb = new StringBuilder(); int iMax = x.GetUpperBound(0); int jMax = x.GetUpperBound(1); int kMax = x.GetUpperBound(2); for (int i = 0; i <= iMax; i++) for (int j = 0; j <= jMax; j++) for (int k = 0; k <= kMax; k++) sb.AppendFormat("{0} ", x[i, j, k]); // 1 2 3 4 ... 24 [Visual Basic .NET] sb = new StringBuilder() Dim j, k As Integer Dim iMax as Integer = x.GetUpperBound(0) Dim jMax as Integer = x.GetUpperBound(1) Dim kMax as Integer = x.GetUpperBound(2) For i = 0 To iMax For j = 0 To jMax For k = 0 to kMax sb.AppendFormat("{0} ", x(i, j, k)) ' 1 2 3 4 ... 24 Next k Next j Next i
GetLowerBound を呼び出していないことに注意してください。 多くの配列の下限、さらに CLS 準拠の配列の下限が 0 なので、 ほとんどの場合、呼び出しを 0 に置き換えることができます。 また、入れ子ループ内で何度も GetUpperBound を呼び出すのではなく、 一時変数を使用しました。 (上限用の定数を宣言して使用すると、さらによくなります。)
配列または配列要素の変換 (配列の共分散)
参照型の配列では、 2 つの配列の型が同じランクで、 ある型から別の型に暗黙または明示的に変換すると、 ある配列の型を別の配列の型に変換できます。 たとえば、string から Object への暗黙の変換が存在するので、 Object の配列を使用できる場所ならどこでも string の配列を使用できます。
共分散が原因で、配列要素の割り当てでは、型を確認する必要があります。
例は次のとおりです。
[C#] public static string PrintRefArrayToString(object [] oa, string name) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < oa.Length; i++) sb.AppendFormat("{0}[{2}] is {1}\n", name, oa[i], i); return sb.ToString(); } void FillArray(object [] oa, object o) { for (int i = 0; i < oa.Length; i++) // 割り当てられた要素の型を確認済みです! oa[i] = o; } // ... StringBuilder sb = new StringBuilder(); string [] s = {"One", "Two", "Three"}; sb.Append(PrintRefArrayToString(s, "s")); FillArray(s, "Hello"); sb.Append(PrintRefArrayToString(s, "s")); FillArray(s, null); sb.Append(PrintRefArrayToString(s, "s")); // FillArray(s, 0); // 割り当ての例外によって失敗します。 // int [] ia = {0, 1, 2}; // FillArray(ia, 0); // 失敗します。値型の共分散がありません。 [Visual Basic .NET] Function PrintRefArrayToString(ByVal oa() As Object, _ Byval name As String) As String Dim sb As New StringBuilder() Dim i As Integer For i = 0 To oa.Length - 1 sb.AppendFormat("{0}({2}) is {1}\n", name, oa(i), i) Next sb.Replace("\n", Environment.NewLine) Return sb.ToString() End Function Sub FillArray(oa() as Object, o as Object) Dim i as Integer For i = 0 To oa.Length ? 1 ' 1 つずらします。 ' 割り当てられた要素の型を確認済みです! oa(i) = o Next End Sub ' ... Dim sb As New StringBuilder() Dim s() As String = {"One", "Two", "Three"} sb.Append(PrintRefArrayToString(s, "s")) FillArray(s, "Hello") sb.Append(PrintRefArrayToString(s, "s")) FillArray(s, Nothing) sb.Append(PrintRefArrayToString(s, "s")) ' FillArray(s, 0) ' 割り当ての例外によって失敗します。 ' Dim ia() As Integer = {0, 1, 2} ' FillArray(ia, 0) ' 失敗します。値型の共分散がありません。
ただし、既に説明したように、配列の共分散は参照型にしか適用されません。 値型の配列 (int または Integer の配列など) をその他の配列型には変換できません。 さらに、Object の配列にも変換できません。 変換するには、値型の配列のコピーを作成する必要があります。 もちろん、すべての配列は System.Array または Object に変換できます。
配列の配列 (または "不規則行" の配列)
.NET Framework は、配列の配列をサポートします。 (Visual Basic .NET は、Beta 1 ではサポートしていませんでしたが、現在はサポートしています。 これはよいことです。)
配列の配列は、たとえば各行の長さが異なる 2 次元配列などに便利です。 各行の配列が別に割り当てられるので、 各行をそれぞれ必要な長さにできます。 つまり、すべての行を同じ長さにする必要はありません。
このアクセスは、 (逆参照と配列の境界の確認を 1 回以上実行する必要があるので) 実際の多次元配列へのアクセスより少し遅くなり、 その構築には、より多くのメモリ割り当てと時間がかかります。 ただし、行のサイズがまったく異なり、 データ構造が大規模な場合、 かなり多くのメモリを節約できます。
配列の配列を作成して初期化するには、 常に初期化のためのループが必要になります。 これは、主となる配列を作成するのと同じステートメントでは、 下位の配列を作成できないためです。 たとえば、 可変長の int または Integer の配列を 20 個を含む配列が必要な場合は、 次のコードを記述します。
[C#] int [][] arrOfArr = new int [5][]; // ここでは、行のサイズを指定できません。 for (int i = 0; i <= 4; i++) { int rowLen = i + 3; // 各行に異なるサイズを設定します。 arrOfArr[i] = new int[rowLen]; // 下位の配列を作成します。 for (int j = 0; j < rowLen; j++) // 下位の配列の要素を初期化します。 arrOfArr[i][j] = i * 100 + j; } StringBuilder sb = new StringBuilder(); foreach (int[] ia in arrOfArr) { // 入れ子になっている foreach を使用する必要があります。 foreach (int i in ia) sb.AppendFormat("{0} ", i); sb.Append("\n"); } [Visual Basic .NET] ' 注意 : Visual Basic は、自動的にもう 1 つの要素を追加します。 ' そのため、1 つ少なく割り当てます。 Dim arrOfArr(4)() as Integer ' 行のサイズを指定できません。 Dim i as Integer For i = 0 to 4 Dim rowLen as Integer = i + 3 ' 各行に異なるサイズを設定します。 arrOfArr(i) = New Integer(rowLen - 1) {} ' 下位の配列を作成します。 Dim j as Integer For j = 0 To RowLen - 1 ' 下位の配列の要素を初期化します。 arrOfArr(i)(j) = i * 100 + j Next j Next i Dim sb as New StringBuilder Dim ia() as Integer For Each ia in arrOfArr ' 入れ子になっている For Each を使用する必要があります。 ' i を再使用します。 For Each i in ia sb.AppendFormat("{0} ", i) Next sb.Append(Environment.NewLine) Next
配列の配列では、 1 回で foreach または For Each を実行することはできませんが、 入れ子になっている foreach または For Each を実行できることに注意してください。
試してみましょう!
.NET Framework 学習チームのメンバは誰ですか?
名医は、.NET を 1 人で楽しむだけではなく、他の人たちと一緒に作業することも願っています。 そのほうがより面白く、たくさんのことを学べること請け合いです。
ちょっと試してみましょう...
配列と文字列、そして文字列の配列で遊んでみましょう。 少なくとも 1 次元配列と 2 次元配列については必ず試してください。 Conway のライフ ゲームなど、配列を基にしたゲームをやってみましょう (次回、やる予定です)。
盤面は、最初のパターンを作成する 2 次元の行列です。 行列内の各セルは、以下の規則に基づいて、生存、死亡または誕生します。 説明は、以下のとおりです。
ライフ ゲームは、(みなさんご存知のとおり) John Conway 氏によって考案されました。 このゲームは、セルのフィールド上で行われます。 各セルは、8 個のセル (隣接するセル) と隣り合っています。 セルは、(生命体が) 生存するかしないかのいずれかです。 前の世代から世代を受け継ぐための規則は、以下のとおりです。
- 死亡 : 生存するセルに 0 個、1 個、4 個から 8 個の生存するセルが隣接している場合、 生命体は死亡します (隣接するセルが 0 個または 1 個の場合は寂しくて死亡し、 4 個から 8 個の場合は過密状態により死亡します)。
- 生存 : 生存するセルに 2 個または 3 個の生存するセルが隣接している場合、 生命体は次の世代まで生き残ります。
- 誕生 : 空白のセルに 3 個の生存するセルが隣接している場合、 そのセルに新しい生命が誕生します。
ASP.NET アプリケーションまたは Microsoft® Windows® Forms アプリケーションでデータ連結を使用してみてください。 アプリケーションがどのように動作するかわかります...
まとめと次回予告
今回は、配列について説明しました。 次回は、ASP.NET アプリケーションとして John Conway のライフ ゲームを使用し、配列について説明します。
0 コメント:
コメントを投稿