Компилятор C# + универсальный код с ограничениями boxing +


Рассмотрим код 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 не использует метаданные ограничений для создания более эффективного универсального кода? Распаковывать.любой дает небольшие накладные расходы (просто 4X-5X медленнее), но почему бы не создать лучший код в этом сценарии?

2 3

2 ответа:

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

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: = null, если S-объект тип или интерфейс
  7. S []: = T [] если S := T и массивы являются либо обоими векторами (основанными на нуле, ранг один), либо ни одним из них является вектором и оба имеют одинаковый ранг. (Это правило имеет дело с ковариацией массива.)
  8. Если S и T являются указателями методов, то S: = T, если сигнатуры (возвращаемые типы, типы параметров и условность вызова) одинаковы.

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

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

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

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

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

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

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

where T : struct, U
where U : class

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