Компилятор C # + универсальный код с боксом + ограничения - PullRequest
3 голосов
/ 02 ноября 2009

Давайте рассмотрим код MSIL, сгенерированный для следующего универсального метода:

public static U BoxValue<T, U>(T value)
  where T : struct, U
  where U : class
{
  return value;
}

Посмотрите:

.method public hidebysig static !!U  BoxValue<valuetype .ctor
 ([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed
{
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  unbox.any  !!U
  IL_000b:  ret
}

Но для общего кода, приведенного выше, более эффективное представление IL должно быть:

  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  ret

Из ограничений известно, что значение заключено в ссылочный тип . Код операции Unbox.any полностью избыточен, поскольку после кода операции box значение в стеке IL будет уже действительной ссылкой на !!U, которое можно использовать без распаковки.

Почему компилятор C # 3.0 не использует метаданные ограничений для создания более эффективного универсального кода? Unbox.any дает небольшие накладные расходы (только в 4–5 раз медленнее), но почему бы не генерировать лучший код в этом сценарии?

Ответы [ 2 ]

6 голосов
/ 14 ноября 2009

Похоже, что компилятор делает это из-за некоторых проблем с верификатором.

IL, который вы хотите, чтобы компилятор генерировал, не подлежит проверке, и поэтому компилятор C # не может его сгенерировать (весь код C # за пределами "небезопасных" контекстов должен проверяться).

Правила «проверки совместимости типов» приведены в Разделе 1.8.1.2.3, Часть III спецификации Ecma.

Они говорят, что тип 'S' совместим по верификации с типом 'T' или (S: = T), используя следующие правила:

  1. [: = является рефлексивным] Для всех типов проверки S, S: = S
  2. [: = является транзитивным] Для всех типов проверки S, T и U, если S: = T и T: = U, то S: = U.
  3. S: = T, если S - это базовый класс T или интерфейс, реализованный с помощью T, а T не является типом значения.
  4. object: = T, если T является типом интерфейса.
  5. S: = T, если S и T оба являются интерфейсами, а реализация T требует реализации из S
  6. S: = ноль, если S - тип объекта или интерфейс
  7. S []: = T [], если S: = T и массивы либо оба вектора (основаны на нуле, ранг один), либо ни является вектором, и оба имеют одинаковый ранг. (Это правило касается ковариации массива.)
  8. Если S и T являются указателями метода, то S: = T, если подписи (возвращаемые типы, типы параметров и Соглашение о вызове) одинаковы.

Из этих правил единственное, что может быть применимо в этом случае, это # ​​3.

Однако # 3 не применяется к вашему коду, потому что «U» не является базовым классом «T» и не является базовым интерфейсом «T», поэтому проверка «или» возвращает false.

Это означает, что необходимо выполнить НЕКОТОРЫЕ инструкции, чтобы преобразовать коробочный T в U таким образом, чтобы он проходил через верификатор.

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

Технически, однако, компилятор делает «правильную» вещь, основываясь на спецификации ECMA.

Вам следует сообщить об ошибке кому-нибудь в Microsoft.

3 голосов
/ 02 ноября 2009

Эти ограничения выглядят странно:

where T : struct, U
where U : class

T является типом значения, но в то же время должен наследовать от U, который является ссылочным типом. Интересно, какие типы могут удовлетворять вышеуказанным ограничениям и позволять нам вызывать этот метод.

...