Twittter上のhttps://twitter.com/leppie/status/1052138293410897920にて、以下のようなC#クイズがありました。
C# quiz
class Program
{
static void Main()
{
Bar("Baz");
var i = Foo<string>("Baz"); // what is the type of var?
}
static void Bar(dynamic d)
{
var i = Foo<string>(d); // what is the type of var?
}
static T Foo<T>(dynamic d)
{
return (T) d;
}
}
正解は、1つめのvarはstring型、2つめのvarはdynamic型になります。
しかし、Foo<string>(d)は絶対にstring型を返すはずです。なぜvar iはstring型にならず、dynamic型になるのでしょうか。
以上が設問です。
解答編 §
さーて、ない知恵をひねって答をひねり出してみました。
この答で何点が付くのかな。
ともかくひねり出した答を書いてみよう。
これを解釈するためには、特殊な挙動や解釈は不要です。ドキュメント化されていない特別な振る舞いや、コンパイラのバグを想定する必要もありません。
これを解釈するための必要な知識はこれだけです。
dynamic (C# リファレンス)より
ただし、dynamic 型の式を含む演算はコンパイラによって解決または型チェックされません。
つまり、Foo<string>(d)はdynamic 型の式を含む演算であるため、コンパイラが解釈を行いません。ですから、この初期化式には型がありません。しかし、初期化式があればvarによる変数宣言は可能です。では、変数にはどのような型が想定されるのか。dynamic型を当てはめるしかありません。
そこから逆算すると、このソースは冗長です。はっきり言って型引数は何の関係もありません。以下のように書けば、【必ずstring型を返すと決まっている関数を初期化式に使っているのに変数がdynamic型になってしまう】という現象を再現できます。
class Program
{
private static string sub(string s)
{
return s;
}
static void Main(string[] args)
{
dynamic d = "";
var v1 = sub(d);
}
}
余談 §
ただし、以下の点には注意が必要です。
以下のような宣言では、変数はdynamic型になりません。
var v2 = d as string;
var v3 = (string)d;
var v4 = d is string;
おそらく【dynamic 型の式を含む演算】という説明がポイントです。これは【dynamic 型の式を含む式】ではないのです。たとえば、d as stringであればdは【dynamic 型の式を含む演算】として扱われますが、式全体ではコンパイラによって式が評価されるものと思われます。