Milkのメモ帳

日々の思いつきを忘れないようにのメモ用です。

Milkのメモ帳

【Visual Studio】可変配列(ArrayList、List<T>)の使い方【C#】


f:id:maxminkun:20180119160501p:plain

こんにちは。Milkです。
最近コーディングをするなかで、はまり込んでしまったことを備忘録として書いておきます。

Windows用のアプリケーション。特にデスクトップ向けに、C#を使う人は多いのでは?と思います。

この言語は扱い方がかなり楽な部類の言語だと思います。

なので仕事の関係上ですが、私も完全にJavaからC#に乗り換えてしまって、Javaの記憶が・・・(白目

C言語のときに配列がとても面倒なものでしたけど、可変配列ができてから大分楽になりました。

ArrayList

C#がデビューしてから、しばらくは可変配列の代表格はArrayListでした。

こいつはなかなか優秀でして、優秀すぎるがゆえに使えないという「謎」が発生してましたね(笑)

ArrayListで格納できるものは、「Object型」です。

「全ての道はローマに通ず」ではないですけど、究極を言うとC#で扱う「もの」は全部Object型のクラスに属します。

よって、ArrayList には何でも入れられるわけです。

今はデフォルトで使われていないので、使用する場合はusingで呼び出しておくことが必要。

using System.Collections;

private void button1_Click(object sender, EventArgs e)
{
    ArrayList test = new ArrayList();

    test.Add(1);
    test.Add("文字列");
    test.Add(0.25);

    Console.WriteLine(test[0]);
    Console.WriteLine(test[1]);
    Console.WriteLine(test[2]);

    //ビルドエラーが発生する。
    //オブジェクト同士の演算は不可。よってキャストの必要あり。
    Console.WriteLine(test[0] + test[1] + test[2]);
}

要は、ArrayList に入れたときに、全てはObject型に置き換えられているので、取り出すならキャスト(型変換)をしろということ。

無理にでもビルドエラーを消そうとすると、

//無理に型指定をしているが、型変換が不可能なものが入っていたら演算が成り立たないので、
//実行時にエラーとなる。
Console.WriteLine((int)test[0] + (int)test[1] + (int)test[2]);

こんな書き方になります。もとと異なる型で取り出そうとすることが出来てしまうけれど、変換出来ずにエラーで落ちるので、入れ込んだデータのもとの型を覚えている必要があります。

何でも入れられるけれど、取り出すときにもとの型を覚えておく必要がある。

同じデータ型で統一していたとしても、他のプログラマが参照した時に誤って型変換してしまう可能性がある。

ということで、より「型」の指定を明確にしようとなって、List が出てきました。

それにObject型に変換がかかるから、処理速度が落ちることも問題だったような気がする・・・

今は、あまり使う人も少ないね。

List <T>

最初は、これを理解するのに苦労したというか・・・何を意味しているのかわからんちんでした(笑)

でも、ソースを見てもらえれば何をしたらいいかは分かると思います。

これは、デフォルトでusingの呼び出しがされていると思う。

using System.Collections.Generic;

private void button1_Click(object sender, EventArgs e)
{
    List<string> test = new List<string>();

    test.Add("1");
    test.Add("文字列");
    test.Add("0.25");

     Console.WriteLine(test[0]);
     Console.WriteLine(test[1]);
     Console.WriteLine(test[2]);

     //これは文字列の連結として解釈される
     Console.WriteLine(test[0] + test[1] + test[2]);
}

<T> が何なんだ!って思うかもしれない。

本来は Generic という考えのうちの一つなのですけど、そこを解説しだすと長くなるというか・・・

とりあえずは、「可変配列に型指定ができるようにしようよ。」ってことだね。

こうすれば、データがどの型なのかを誤ることが少なくなる。

そもそもが、指定した型のデータしか格納出来ない。だから、取り出したものは同様の型であるのは間違いないってわけ。

List <T> のコピー

では、List を別の List にコピーしようとしたらどうなるか。

int型 や String型 なら代入で問題ない。

しかし、List を代入でコピーしようとすると、大変なことになる。

private void button1_Click(object sender, EventArgs e)
{
    List<string> test = new List<string>();

    test.Add("1");
    test.Add("文字列");
    test.Add("0.25");

    List<string> test2 = new List<string>();
    test2 = test;

    test2[0] = "テスト";

    Console.WriteLine(test[0]);
    Console.WriteLine(test[1]);
    Console.WriteLine(test[2]);

    //値を変えると、test と test2 に影響が出る。
    Console.WriteLine(test[0] + test[1] + test[2]);            
}

コンソールに値を出しているので、結果をみてみよう。

そうすると・・・

テスト
文字列
0.25
テスト文字列0.25

ん?なんだこれは?! なぜ、test の値が変わっているのだ?!

それはね・・・代入すると、参照渡しになるから。

参照渡しって、結局はポインタを渡してるんですよ。だから、test も test2 も同じポインタ(メモリの番地)を共有してることになり、片方でも書き換えると両方に影響が出ます。

もし、別のものとしてコピーしたいなら、違うポインタを割り当てないといけない。

ここで使えるのはインスタンス生成時にコンストラクターを使うということ。

private void button1_Click(object sender, EventArgs e)
{
    List<string> test = new List<string>();

    test.Add("1");
    test.Add("文字列");
    test.Add("0.25");

    //test の値を使って新しくポインタを割り当てる。
    List<string> test2 = new List<string>(test);

    test2[0] = "テスト";

    Console.WriteLine(test[0]);
    Console.WriteLine(test[1]);
    Console.WriteLine(test[2]);

    Console.WriteLine(test[0] + test[1] + test[2]);            
    Console.WriteLine(test2[0] + test2[1] + test2[2]); 
}

結果は以下のようになる。

1
文字列
0.25
1文字列0.25
テスト文字列0.25

ね? ちゃんとポインタは分離してるでしょ?

Insert

可変配列の良いところは、値挿入が出来るところ。

Insert( ) を使えばよろし。

private void button1_Click(object sender, EventArgs e)
{
    List<string> test = new List<string>();

    test.Add("1");
    test.Add("文字列");
    test.Add("0.25");

    test.Insert(1, "2番目");

    Console.WriteLine(test[0]);
    Console.WriteLine(test[1]);
    Console.WriteLine(test[2]);
    Console.WriteLine(test[3]);
}

結果は以下になる。

1
2番目
文字列
0.25

ちゃんと2番目に値が入ってる。

ただし注意があって、配列なので以下の書き方が出来てしまうが、意味が異なる。

test[1].Insert(1, "2番目");

誤ってこれを書くと、testの2番目(配列の[1])の値の中で、indexが1の場所になんとかねじ込もうとします。

つまり、test[1] の値である「文字列」の中で、「字」場所にInsertを行おうとがんばります。

これを意図して組むなら問題ないですけど、書き間違いならば値が思わぬものに書き換わるか、null参照で落ちるかなどのバグが発生します。

知らずのうちに、自分も書いちゃってバグ箇所が見つからないという落とし穴にハマってしまっていました。

何しろ、動作しちゃう(値の中にInsert出来てしまう)ことがあったりして、挙動を掴むのが難しい。

注意ですね・・・(;・∀・)

最後に

可変配列は非常に扱いやすく多様もしがちです。

それ自体は問題ではないですけれど、処理速度を考えると固定配列の方が速い。

だから、上手く使い分けて行きたいですね!

では、今回はこの辺で。

adios!!