Как "оператор" переводится, когда правый операнд является общим?

Я только что отправил ответ на этот вопрос, но я не совсем уверен в своем ответе. Есть две вещи, которые мне интересно, рассмотрите этот код:

class Foo<T>
{ 
    void SomeMethod()
    {
        string str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

Согласно C# Specification 5.0, существует два разных типа преобразования as operator.

Если тип времени компиляции E не dynamic, операция E as T дает тот же результат, что и

E is T ? (T)(E) : (T)null

Если тип времени компиляции E равен dynamic, в отличие от оператора трансляции as operator не динамически связан (§7.2.2). Поэтому разложение в этом случае есть:

E is T ? (T)(object)(E) : (T)null

Так как это недопустимо из-за (Foo<T>)str

str is Foo<T> ? (Foo<T>)str : (Foo<T>)null;

Я думал, что это нужно перевести как:

str is Foo<T> ? (Foo<T>)(object)str : (Foo<T>)null;

Но спецификация говорит, что это происходит только тогда, когда тип E равен dynamic.

Итак, мои вопросы:

  • Является ли компилятор преобразованием этого выражения в код, который обычно недействителен?
  • Когда тип E является динамическим, почему сначала он отбрасывает E в object, а затем T, а (T)E является полностью допустимым?

Ответы

Ответ 1

Является ли компилятор преобразованием этого выражения в код, который обычно недействительным?

Когда я смотрел на спецификацию около часа, я начинаю убеждать себя, что это просто край, который был упущен в спецификации. Обратите внимание, что это всего лишь способ для композиторов языка С# выразить оператор as с семантикой оператора is.

Компилятор фактически не конвертирует оператор as в тернарный оператор с is. Он выдает вызов IL на isinst, как для as, так и is:

IL_0000: nop
IL_0001: ldstr "foo"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: isinst class ConsoleApplication2.Foo`1<!T>
IL_000d: stloc.1
IL_000e: ret

Глядя на скомпилированную DLL, оператор as остается нетронутым.

Когда тип E динамичен, почему сначала он бросает E в объект, тогда T в то время как (T) E вполне справедливо?

Это описано в мелкой печати спецификации:

Если тип времени компиляции E является динамическим, , в отличие от оператора трансляции оператор as не динамически связан (§7.2.2). Следовательно расширение в этом случае:

E is T ? (T)(object)(E) : (T)null

Приведение в object необходимо для использования as с объектами dynamic. as - это операция времени компиляции, а объекты dynamic привязаны только во время выполнения.

Компилятор действительно рассматривает объекты типа dynamic как тип object для начала:

class Foo<T> 
{
    public void SomeMethod() 
    {
        dynamic str = "foo";
        Foo<T> f = str as Foo<T>;
    }
}

str фактически рассматривается как object для начала:

.class private auto ansi beforefieldinit Foo`1<T>
    extends [mscorlib]System.Object
{
    // Methods
    .method public hidebysig 
        instance void SomeMethod () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 15 (0xf)
        .maxstack 1
        .locals init (
            [0] object,
            [1] class Foo`1<!T>
        )

        IL_0000: nop
        IL_0001: ldstr "foo"
        IL_0006: stloc.0
        IL_0007: ldloc.0
        IL_0008: isinst class Foo`1<!T>
        IL_000d: stloc.1
        IL_000e: ret
    } // end of method Foo`1::SomeMethod
}

Изменить:

Пообщавшись с Владимиром Решетниковым из команды Managed Languages, он объясняет, что на самом деле семантика представления от оператора "как оператор" до "оператора трансляции" пытается выполнить:

Я согласен, в спецификации тоже есть неточный язык. Он говорит, что оператор "как" всегда применим, если задействован открытый тип, но затем описывает его оценку с точки зрения приведения, что может быть недействительным в некоторых случаях. Он должен сказать, что отливки в расширении не представляют собой обычный оператор С# cast, а просто представляют преобразования, которые разрешены в операциях "как".. Я сделаю заметку, чтобы исправить это. Спасибо!