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

StackOverflow https://stackoverflow.com/questions/1661150

Вопрос

Давайте рассмотрим код 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 раз медленнее), но почему бы не создать лучший код в этом сценарии?

Это было полезно?

Решение

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

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', поэтому проверка 'или' возвращает false.

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

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

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

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

Другие советы

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

where T : struct, U
where U : class

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top