Почему компилятор выдает инструкции для сравнения экземпляров ссылочного типа? - PullRequest
36 голосов
/ 18 декабря 2010

Вот простой универсальный тип с уникальным универсальным параметром, ограниченным ссылочными типами:

class A<T> where T : class
{
    public bool F(T r1, T r2)
    {
        return r1 == r2;
    }
}

Сгенерированный IL с помощью csc.exe:

ldarg.1
box        !T
ldarg.2
box        !T
ceq

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

Но если ограничение указывает, что «T» никогда не должно быть типом значения, почему компилятор пытается блокировать r1 и r2?

Ответы [ 2 ]

43 голосов
/ 18 декабря 2010

Требуется удовлетворить ограничения проверяемости для сгенерированного IL. Обратите внимание, что не поддается проверке не обязательно означает неверно . Он прекрасно работает без инструкции box, если его контекст безопасности позволяет запускать непроверяемый код. Проверка является консервативной и основана на фиксированном наборе правил (например, достижимость ). Чтобы упростить задачу, они решили не заботиться о наличии ограничений общего типа в алгоритме проверки.

Спецификация инфраструктуры общего языка (ECMA-335)

Раздел 9.11: Ограничения общих параметров

... Ограничения на универсальный параметр ограничивают только те типы, которые универсальный параметр может быть создан с. Проверка (см. Раздел III) требует, чтобы поле, свойство или метод общий параметр , как известно, обеспечивает путем удовлетворения ограничения , не может быть напрямую доступным / вызванным через универсальный параметр , если только он не заключен в первый квадрат (см. Раздел III) или инструкция callvirt имеет префикс инструкции constrained. ...

Удаление инструкций box приведет к непроверяемому коду:

.method public hidebysig instance bool 
       F(!T r1,
         !T r2) cil managed
{
   ldarg.1
   ldarg.2
   ceq
   ret
}


c:\Users\Mehrdad\Scratch>peverify sc.dll

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[IL]: Error: [c:\Users\Mehrdad\Scratch\sc.dll : A`1[T]::F][offset 0x00000002][fo
und (unboxed) 'T'] Non-compatible types on the stack.
1 Error(s) Verifying sc.dll

ОБНОВЛЕНИЕ (Ответ на комментарий): Как я уже упоминал выше, проверяемость не эквивалентна правильности (здесь я говорю о «правильности» с точки зрения безопасности типов). Проверяемые программы являются строгим подмножеством правильных программ (то есть все проверяемые программы наглядно верны, но есть правильные программы, которые не поддаются проверке). Таким образом, проверяемость является более сильным свойством, чем правильность. Поскольку C # является языком, полным по Тьюрингу, Теорема Райса гласит, что доказательство правильности программ в общем случае неразрешимо.

Давайте вернемся к моей достижимости аналогии, поскольку это легче объяснить. Предположим, вы разрабатывали C #. Одна вещь, о которой подумал, - это когда выдавать предупреждение о недоступном коде и вообще удалять этот кусок кода в оптимизаторе, но как вы собираетесь обнаруживать весь недоступный код? Опять же, теорема Райс говорит, что вы не можете сделать это для всех программ. Например:

void Method() {
    while (true) {
    }
    DoSomething();  // unreachable code
}

Это то, о чем на самом деле предупреждает компилятор C #. Но это не предупреждает о:

bool Condition() {
   return true;
}

void Method() {
   while (Condition()) {
   }
   DoSomething();  // no longer considered unreachable by the C# compiler
}

Человек может доказать, что поток управления никогда не достигает этой линии в последнем случае. Можно утверждать, что компилятор может статически доказать, что DoSomething также недоступен в этом случае, но это не так. Зачем? Дело в том, что вы не можете сделать это для всех программ, поэтому вы должны нарисовать линию в в некоторой точке . На этом этапе вы должны определить свойство decidable и назвать его «достижимость». Например, для достижения, C # придерживается константных выражений и вообще не будет смотреть на содержимое функций. Простота анализа и согласованность дизайна являются важными целями при принятии решения о том, где провести черту.

Возвращаясь к нашей концепции проверяемости, это аналогичная проблема. Проверяемость, в отличие от правильности, является решаемым свойством. Как разработчик среды выполнения, вы должны решить, как определить проверяемость, исходя из соображений производительности, простоты реализации, простоты спецификации, согласованности, облегчая компилятору уверенную генерацию проверяемого кода. Как и большинство дизайнерских решений, в нем много компромиссов. В конечном счете, разработчики CLI решили, что они предпочитают вообще не рассматривать общие ограничения при проверке на проверяемость.

15 голосов
/ 18 декабря 2010

Ответ Мехрдада довольно хороший; Я просто хотел добавить пару очков:

Во-первых, да, в этом случае это просто для того, чтобы проверяющий был доволен. Дрожание должно, конечно, оптимизировать инструкцию по боксу, так как не имеет смысла вставлять ссылочный тип.

Тем не менее, существуют случаи, когда для того, чтобы проверяющий был доволен, мы должны ввести инструкции по боксу, которые не были оптимизированы. Например, если вы говорите:

class B<T> { public virtual void M<U>(U u) where U : T {...} }
class D : B<int> 
{ 
    public override void M<U>(U u)
    {

Компилятор C # знает, что в D.M U может быть только int. Тем не менее, для того, чтобы быть проверяемым, существуют ситуации, когда вы должны быть упакованы в объект и затем распакованы в int. Джиттер не всегда оптимизирует их; мы указали команде по джиттеру, что это возможная оптимизация, но ситуация настолько неясна, что вряд ли приведет к большой победе для многих реальных клиентов. Существуют более эффективные оптимизации, на которые они могли бы тратить свое время.

...