1
1

More than 5 years have passed since last update.

[小ネタ] リストと配列のあいだ

Last updated at Posted at 2015-05-30

元ネタが古すぎるのはまず置いておいて、重箱の隅つついたら、不思議な差異があったので、まとめてみた

値型のプロパティを変更する

例えば以下のような値型を作ったとして

    struct SampleValueType
    {
        public SampleValueType(int value) : this()
        {
            Value = value;
        }

        public int Value { get; set; }
    }

こいつをLength=1の配列にした場合、以下のようになる

        private static void Main(string[] args)
        {
            SampleValueType[] array =  {new SampleValueType(0)};


            //当然0が表示される。
            Console.WriteLine(array[0].Value);
        }

さてこのとき、以下のように書き換えたらコンパイラは通るだろうか?(答え:通る)


        private static void Main(string[] args)
        {
            SampleValueType[] array =  {new SampleValueType(0)};


            //当然0が表示される。
            Console.WriteLine(array[0].Value);

            array[0].Value = 42;

            //書き換わった42が表示される。
            Console.WriteLine(array[0].Value);
        }

普通に考えると、コンパイラエラーのCS1612が発生してもおかしくない状況だけど、普通に通せるし、意図通りの挙動にもなる。
ちなみに、以下のようにList<SampleValueType>にすると、想定通りCS1612が発生する。


        private static void Main(string[] args)
        {
            List<SampleValueType> list =new List<SampleValueType>{new SampleValueType(0)};


            //当然0が表示される。
            Console.WriteLine(list[0].Value);


            //CS1612が発生する。
            list[0].Value = 42;

        }

なぜ、配列に対しては問題なくて、Listには問題が発生するかというと、[]演算子がListはインデクサのオーバーロードになる。その結果プロパティ呼び出しになるので、以下のようなコードと意味的に同じようになり、コンパイラがエラーを出してくれる。

        private static void Anatomy()
        {
            List<SampleValueType> list = new List<SampleValueType> {new SampleValueType(0)};

            //一時変数に代入
            SampleValueType anonymous = list[0];

            //一時変数の値を変えるだけ。
            anonymous.Value = 42;

            //当然元は変化しない。
            Console.WriteLine(list[0].Value);
        }

んでは、なぜ配列に対する同様の操作が許容されるかというと、配列型の変数に対して、[]演算子をそのまま適用しているので、添え字指定された配列の要素そのものに対する操作となるため、操作が許容されるからじゃないかなと。

ちなみに、以下のようにIListへ代入した場合は、当然インデクサ経由と解釈されるので、CS1612が発生するので、ご注意の程・・・


        private static void Main(string[] args)
        {

            SampleValueType[] array =  {new SampleValueType(0)};
            IList<SampleValueType> list = array;

            //当然0が表示される。
            Console.WriteLine(array[0].Value);


            //CS1612が発生する。
            //list[0].Value = 42;

        }

foreachの中で配列の要素を変更する

こんなことをすれば、鼻から悪魔は出るし、魔法は尻から出ることになるからやっちゃいけない。
けど、やっちゃった場合、挙動に差が出る。


        private static void Main(string[] args)
        {
            SampleValueType[] array = {new SampleValueType(0), new SampleValueType(1)};
            List<SampleValueType> list = new List<SampleValueType>(array);


            //InvalidOperationExceptionは発生しない。
            foreach (var elem in array)
            {
                array[1] = new SampleValueType(42);

                //0
                //42
                //と表示される。
                Console.WriteLine(elem.Value);
            }

            //InvalidOperationExceptionは発生しない。
            IList<SampleValueType> hoge = array;
            foreach (var elem in hoge)
            {
                array[1] = new SampleValueType(42);

                //0
                //42
                //と表示される。
                Console.WriteLine(elem.Value);
            }


            //InvalidOperationExceptionが発生する。
            foreach (var elem in list)
            {
                list[0] = new SampleValueType(42);

                Console.WriteLine(elem.Value);
            }
        }

このような場合、Listの場合は、IndexerのSetterが呼ばれたかどうかで、内容変更の可否を判定できるけど、配列の場合はそのようなタイミングが存在しないために、このような結果になるんじゃ無いかなと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1