元ネタが古すぎるのはまず置いておいて、重箱の隅つついたら、不思議な差異があったので、まとめてみた
値型のプロパティを変更する
例えば以下のような値型を作ったとして
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が呼ばれたかどうかで、内容変更の可否を判定できるけど、配列の場合はそのようなタイミングが存在しないために、このような結果になるんじゃ無いかなと思います。