【入門級】配列を同じ値で初期化する
配列初期化の問題点 §
new演算子で配列を作成すると、その型のデフォルト値で初期化されます。たいていはゼロかnullですが、中には0001/01/01 0:00:00で初期化されるDateTime型のような変わり種もあります。
C#のコツとして、これらの値が初期値になるように設計しておくと初期化の手間が減るというノウハウがあります。
ところが、いつもその値で済む訳ではありません。
効率よく他の値で配列を初期化するにはどうすれば良いでしょうか。配列の要素に全て同じ値を入れるという前提で話を進めます。
文字型の場合 §
サンプルソースを見ると分かる通り、文字型の配列は文字列からToCharArrayメソッドで簡単に生成できます。そして文字列のコンストラクタに任意の文字を任意の回数繰り返した文字列を作成するバリエーションがあります。この2つを組み合わせると簡単です。
長さが短い場合は直接"AAAA……"のように書いてもOKです。
LINQが使える場合はToCharArrayメソッドの代わりにToArrayメソッドも使用できます。これで4文字短くなります。
全面的にLINQを使うならEnumerableメソッドを使うこともできます。これは同じ値を指定回数繰り返すシーケンスを生成するメソッドです。そのシーケンスを配列に直せば指定した値を指定回数繰り返した配列が得られます。
任意の型の場合 §
やや長くなるEnumerableメソッドのメリットは、任意の型に使用できることです。文字列を分解して文字の配列に加工する方法は文字にしか使えません。しかし、LINQは任意の型に使用できます。
しかし、参照型の場合は意図しない結果になる可能性があることに注意が必要です。普通にEnumerableメソッドを使うと、一つのインスタンスへの参照で配列を初期化することになります。
もし、配列の1要素ごとに別のインスタンスを割り当てたいのであれば、new演算子をEnumerableメソッドの外に出し、確実に指定回数new演算子が実行されるように工夫します。
罠の数々 §
- LINQが使えるかどうかで手数が変わる。ソース先頭にusing System.Linq;があるか、チェックしておこう
- 文字の配列だけは特に方法が多い
- 要素数が少ないときと多いときでは有利な記述方法が変わるかも知れない
- 参照型に適用するときは、同じインスタンスへの参照で初期化して良いのか、それとも1要素ごとにインスタンスを作成すべきか、きちんと把握しておこう
- サンプルソースのMyReferType[] a6 = Enumerable.Repeat(0, 10).Select(c => new MyReferType()).ToArray();という行の0はダミー値であって使用されない。注意しよう。
参考リンク §
String.ToCharArray メソッド
Enumerable.ToArray<TSource>(IEnumerable<TSource>) メソッド
Enumerable.Repeat<TResult>(TResult, Int32) メソッド
Enumerable.Select メソッド
リポジトリ §
https://github.com/autumn009/cshowto
InitArray §
using System;
using System.Linq;
class Program
{
class MyReferType
{
internal int X { get; set; }
public override string ToString() => X.ToString();
}
private static void dump<T>(T[] ar)
{
Console.WriteLine(string.Join(',', ar));
}
static void Main()
{
Console.WriteLine("オーソドックスな方法。サイズ10の配列を作ってから1つ1つ入れる");
char[] a0 = new char[10];
for (int i = 0; i < a0.Length; i++) a0[i] = 'A';
dump(a0);
Console.WriteLine("文字列\"AAAAAAAAAA\"を作ってからToCharArrayで文字の配列化する");
char[] a1 = new string('A', 10).ToCharArray();
dump(a1);
Console.WriteLine("LINQが使えるならToArrayでも良い");
char[] a1linq = new string('A', 10).ToArray();
dump(a1linq);
Console.WriteLine("文字列\"AAAAAAAAAA\"を直接書いてしまう");
char[] a2 = "AAAAAAAAAA".ToArray();
dump(a2);
Console.WriteLine("LINQを使う");
char[] a3 = Enumerable.Repeat('A', 10).ToArray();
dump(a3);
Console.WriteLine("LINQ方式だと型に関係ない");
decimal[] a4 = Enumerable.Repeat(1.2m, 10).ToArray();
dump(a4);
Console.WriteLine("参照型だと意図しない結果になる可能性も");
MyReferType[] a5 = Enumerable.Repeat(new MyReferType(), 10).ToArray();
Console.WriteLine("この時、全ての要素は0だが");
dump(a5);
a5[0].X = 1;
Console.WriteLine("全ての要素が1になってる!");
dump(a5);
Console.WriteLine("これが望んだ結果ではないときはこうする");
MyReferType[] a6 = Enumerable.Repeat(0, 10).Select(c => new MyReferType()).ToArray();
Console.WriteLine("この時、全ての要素は0だが");
dump(a6);
a6[0].X = 1;
Console.WriteLine("最初の要素だけ1になってる!");
dump(a6);
}
}
実行結果
オーソドックスな方法。サイズ10の配列を作ってから1つ1つ入れる
A,A,A,A,A,A,A,A,A,A
文字列"AAAAAAAAAA"を作ってからToCharArrayで文字の配列化する
A,A,A,A,A,A,A,A,A,A
LINQが使えるならToArrayでも良い
A,A,A,A,A,A,A,A,A,A
文字列"AAAAAAAAAA"を直接書いてしまう
A,A,A,A,A,A,A,A,A,A
LINQを使う
A,A,A,A,A,A,A,A,A,A
LINQ方式だと型に関係ない
1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2
参照型だと意図しない結果になる可能性も
この時、全ての要素は0だが
0,0,0,0,0,0,0,0,0,0
全ての要素が1になってる!
1,1,1,1,1,1,1,1,1,1
これが望んだ結果ではないときはこうする
この時、全ての要素は0だが
0,0,0,0,0,0,0,0,0,0
最初の要素だけ1になってる!
1,0,0,0,0,0,0,0,0,0
|