Что напечатает эта программа?
using System;
class A
{
public static void Foo(dynamic d)
{
PrintType(d);
}
static void PrintType<T>(T x)
{
Console.WriteLine(typeof(T));
}
}
class B
{
struct S { }
class C {}
delegate void D();
static void Main()
{
A.Foo(new S[0]);
A.Foo(new C[0]);
A.Foo(new D[0]);
}
}
Можно немного расширить задачу:
static void Main()
{
A.Foo(new S[0]);
A.Foo(new C[0]);
A.Foo(new D[0]);
A.Foo(new Func<S>[0]);
A.Foo(new Func<C>[0]);
A.Foo(new Func<D>[0]);
}
Здравствуйте, nikov, Вы писали:
N>Что напечатает эта программа?
N>N>using System;
N>class A
N>{
N> public static void Foo(dynamic d)
N> {
N> PrintType(d);
N> }
N> static void PrintType<T>(T x)
N> {
N> Console.WriteLine(typeof(T));
N> }
N>}
N>class B
N>{
N> struct S { }
N> class C {}
N> delegate void D();
N> static void Main()
N> {
N> A.Foo(new S[0]);
N> A.Foo(new C[0]);
N> A.Foo(new D[0]);
N> }
N>}
N>
Когда запустил код этого этюда, то у меня случился когнитивный диссонанс и я был уверен, что наблюдаемое поведение — просто натуральный баг и успел отправить пару лучей кое-чего команде C# 4.0 dynamic
Ключом понимания этюда было знание о том, что nikov в стремлении к приданию этюду канонической формы, никогда бы не разбил этюд на два типа, если бы реально требовался только один
Итак код напечатает:
System.Array
System.Object[]
System.MulticastDelegate[]
В случае вызовов PrintType<T>(T x) с параметрами, типы которых известны на момент компиляции, компилятор будет выводить тип типа-параметра метода на момент компиляции. В случае с параметрами типа dynamic, процедура вывода типа-параметра будет отложен на момент исполнения, при этом поведение вывода типов времени исполнения стремится соответствовать статическому поведению настолько, насколько это возможно.
Чтобы приблизиться к статическому поведению, надо учитывать такую важную штуку, как область видимости типа. Фактически в качестве типа-параметра T метода PrintType() могут выступать любой из типов, видимых из всех методов, которым видим сам метод PrintType.
Теперь следует обратить внимание на то, что структура S, класс C и тип делегата D являются приватными nested-типами класса B и их область видимости не распространяется на метод A.Foo()
В данной ситуации механизм dynamic начинает пытаться заменить тип на менее конкретный, используя неявные ссылочные преобразования и boxing-преобразования.
В случае первого вызова с передачей массива типа-значения S, механизм dynamic будет перебирать типы в следующем порядке:
System.Object
-> System.Array
-> B.S[]
и остановится на типе System.Array, являющимся видимым для метода A.Foo().
В случае второго вызова ситуация немного изменяется, так как в силу вступает ещё одно неявное ссылочное преобразование, вызванное поддержкой C# ковариантности массивов (массив C[] является массивом object[]):
System.Object
-> System.Array
-> System.Object[]
-> B.C[]
соответственно в этом случае наиболее конкретным типом будет являться object[].
В третьем случае картина принимает следующий вид:
System.Object
-> System.Array
-> System.Object[]
-> System.Delegate[]
-> System.MulticastDelegate[]
-> B.D[]
N>Можно немного расширить задачу:
N>static void Main()
N>{
N> A.Foo(new Func<S>[0]);
N> A.Foo(new Func<C>[0]);
N> A.Foo(new Func<D>[0]);
N>}
N>
Код напечатает:
System.MulticastDelegate[]
System.Func`1[System.Object][]
System.Func`1[System.MulticastDelegate][]
System.Object
-> System.Array
-> System.Object[]
-> System.Delegate[]
-> System.MulticastDelegate[]
-> System.Func`1[B.S][]
Второй случай, играет роль ковариантность параметра Func<T>:
System.Object
-> System.Array
-> System.Object[]
-> System.Delegate[]
-> System.MulticastDelegate[]
-> System.Func`1[System.Object][]
-> System.Func`1[B.S][]
Третий случай:
System.Object
-> System.Array
-> System.Object[]
-> System.Delegate[]
-> System.MulticastDelegate[]
-> System.Func`1[System.Object][]
-> System.Func`1[System.Delegate][]
-> System.Func`1[System.MulticastDelegate][]
-> System.Func`1[B.S][]
Вот и всё, механизм dynamic просто находит первый наиболее конкретный видимый тип.
Отличный этюд!