Компилятор C # + универсальный код с боксом + ограничения
Вопрос
Давайте рассмотрим код 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), используя следующие правила:
- [:= является рефлексивным] Для всех типов проверки S, S := S
- [:= является транзитивным] Для всех типов проверки S, T и U, если S := T и T := U, то S := U.
- S := T, если S является базовым классом T или интерфейсом, реализованным T, а T не является типом значения.
- object := T, если T - тип интерфейса.
- S := T, если S и T оба являются интерфейсами и для реализации T требуется реализация S
- S := null , если S - тип объекта или интерфейс
- S[] := T[] если S := T и массивы являются либо обоими векторами (с нулевым значением, ранг один), либо ни одним из них не является вектором и оба имеют одинаковый ранг.(Это правило касается ковариации массива.)
- Если S и T являются указателями на методы, то S := T, если сигнатуры (возвращаемые типы, типы параметров и соглашение о вызовах) совпадают.
Из этих правил единственным, которое может быть применимо в данном случае, является правило №3.
Однако #3 не применяется к вашему коду, потому что 'U' не является базовым классом 'T', и это не базовый интерфейс 'T', поэтому проверка 'или' возвращает false.
Это означает, что для преобразования вставленного в коробку T в U необходимо выполнить некоторую инструкцию способом, который передаст верификатор.
Я бы согласился с вами в том, что правила проверки должны быть изменены, чтобы генерация нужного вам кода действительно поддавалась проверке.
Технически, однако, компилятор выполняет "правильную" работу на основе спецификации ECMA.
Вам следует сообщить об ошибке кому-нибудь в Microsoft.
Другие советы
Эти ограничения выглядят странно:
where T : struct, U
where U : class
T - это тип значения, но в то же время он должен наследоваться от U, который является ссылочным типом.Интересно, какие типы могли бы удовлетворить вышеуказанным ограничениям и позволить нам вызвать этот метод.