どーいうことだってばよ?
例えば、以下のような構造体(値型)をこさえたとして、
struct SomeStruct
{
public int Value { get; set; }
public override string ToString() => $"Value is {Value}";
}
コレを、dynamic
経由で取り回したときどうなるかというのが今回のお題。
最後までおつきあい頂ければこれ幸いナリ。
複数のdynamic
に値型経由で代入する場合
コレは割と直感的な結果になると思う。
以下のようなコードを書いたとする。
internal static void Main()
{
var val = new SomeStruct {Value = 42};
dynamic dynamA = val;
dynamic dynamB = val;
//Value is 42
Console.WriteLine(dynamA.ToString());
//Value is 42
Console.WriteLine(dynamB.ToString());
dynamA.Value = 114514;
Console.WriteLine();
//Value is 114514
Console.WriteLine(dynamA.ToString());
//Value is 42
Console.WriteLine(dynamB.ToString());
}
この場合、コメントに記載したとおりの出力となる。
このようになる理由は、dynamic DynamA=val;
及びdynamic DynamB=val;
は、共々Valのコピーをボクシングしているので、片方のValueを動かしてももう片方が影響を受けることは無い。
複数のdynamicにobject経由で代入する場合
こちらは若干直感的ではないかも知れない。
同様にコードを書いていこう
internal static void Main()
{
object obj = new SomeStruct {Value = 42};
dynamic dynamA = obj;
dynamic dynamB = obj;
//Value is 42
Console.WriteLine(dynamA.ToString());
//Value is 42
Console.WriteLine(dynamB.ToString());
dynamA.Value = 114514;
Console.WriteLine();
//Value is 114514
Console.WriteLine(dynamA.ToString());
//Value is 114514
Console.WriteLine(dynamB.ToString());
}
この場合、dynamA
のValueに新たな値を代入した場合、dynamB
も同様に値が変更されている。
これは、元がobject型なので、ボクシングが最初から行われており、そのボクシングされた結果の参照先をdynamA
とdynamB
が参照することになるので、参照型のような振る舞いをすることになる。
また、このことからdynamic型の動的呼び出しは外から観測する限り、ボクシングしたまま値型の内部状態を変更可能であると言うことになると思う。
参考まで、下記のようなコードもobject
経由の代入と同じ挙動をとる。
internal static void Main()
{
dynamic dynamA = new SomeStruct {Value = 42};
dynamic dynamB = dynamA;
//Value is 42
Console.WriteLine(dynamA.ToString());
//Value is 42
Console.WriteLine(dynamB.ToString());
dynamA.Value = 114514;
Console.WriteLine();
//Value is 114514
Console.WriteLine(dynamA.ToString());
//Value is 114514
Console.WriteLine(dynamB.ToString());
}
まとめ
代入の差異が挙動に直結するので、このようなことをする際は若干注意が必要かと思いまとめてみました。
特に、object
経由の場合は、一般的な値型の振る舞いと異なり、参照型のそれに近い形になるので、意図的に使う場合は別として、注意をしなければならないと考えます。