Почему сравниваемые ссылки на С# сравниваются без ошибок компилятора?

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

Рассмотрим этот код:

class Program
{
    interface I1 {}
    interface I2 {}
    class C1 : I1 {}
    class C2 : I2 {}

    static void Main(string[] args)
    {
        C1 c1 = new C1();
        C2 c2 = new C2();

        I1 i1 = c1;
        I2 i2 = c2;

        bool x = c1 == c2;
        bool y = i1 == i2;
    }
}

Компилятор говорит, что я не могу сравнить c1 == c2, что и следует. Типы полностью не связаны. Тем не менее, это позволяет мне сравнивать i1 == i2. Я ожидал бы ошибки здесь с ошибкой компиляции, но я был удивлен, обнаружив, что вы можете сравнить любой интерфейс с любым другим, и компилятор никогда не будет жаловаться. Я мог бы сравнить, например, (I1)null == (IDisposable)null и не проблема.

Являются ли интерфейсы не объектами? Являются ли они особым типом ссылок? Мое ожидание было бы в том, что == приведет либо к прямому сравнению сравнения, либо к вызову в конкретный класс виртуальных равных.

Что мне не хватает?

Ответы

Ответ 1

Прежде всего, обратите внимание, что Ганс цитирует правильную часть спецификации, но в редакции спецификации, которую он цитирует, есть опечатка, которая имеет отношение к вашему вопросу. Исправленная спецификация С# 4 гласит:

Предопределенное равенство ссылочного типа операторы требуют, чтобы один из следующее:

(1) Оба операнда представляют собой значение типа, известного как ссылочный тип или буквальное значение null. Кроме того, Явное преобразование ссылок существует из типа любого операнда к типу другого операнда.

(2) Один операнд - это значение типа T где T - параметр типа, а другим операндом является буквальное значение null. Кроме того, T не имеет значения типа.

Если один из этих условия верны, время привязки возникает ошибка.

Это объясняет ваше наблюдение. Существует явное ссылочное преобразование между любыми двумя интерфейсами, потому что любые два экземпляра двух разных интерфейсов могут ссылаться на один и тот же объект. Может быть класс C3, который реализует как I1, так и I2, и вы могли бы провести сравнительное сравнение того же экземпляра C3, один преобразованный в I1, а другой преобразованный в I2.

Ответ 2

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

interface I1 {}
interface I2 {}
class C1 : I1, I2 {}

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

Ответ 3

Это очень хорошо описано в Спецификации языка С#, глава 7.9.6 "Операторы равенства ссылочного типа":

Предопределенное равенство ссылочного типа операторы:

bool operator == (объект x, объект y);
bool operator!= (объект x, объект y);

Операторы возвращают результат сравнения двух ссылок для равенства или неравновесия.

Так как предопределенное равенство ссылочного типа операторы принимают операнды типа объекта, они применяются ко всем типам, которые не объявлять применимый оператор == и оператор!= члены. Наоборот, любое применимое определяемое пользователем равенство операторы эффективно скрывают предопределенное равенство ссылочного типа операторы.

Предопределенная ссылка для операторов равенства типа требуется один из следующее:
• Оба операнда значения ссылочного типа или литерал ноль. Кроме того, стандартный неявный преобразование (§6.3.1) существует из тип любого операнда по типу другой операнд.
 • Один операнд - это значение типа T, где T является параметр типа и другой операнд является буквальным нулем. Кроме того, T не имеет типа значения ограничение.

Если один из этих условия верны, время компиляции возникает ошибка. Значимые последствия эти правила:
• Это ошибка времени компиляции для использования предопределенное равенство ссылочного типа операторам сравнивать две ссылки которые, как известно, различны при время компиляции. Например, если Типы экземпляров экземпляра compile-time двух типов классов A и B, и если ни А, ни В не вытекают из другой, тогда было бы невозможно два операнда, ссылающиеся на то же самое объект. Таким образом, операция считается ошибкой времени компиляции.

Последний абзац - причина, по которой вы получаете ошибку.

Ответ 4

Мое ожидание будет состоять в том, что a == приведет к прямолинейному сравнению или вызову в конкретный класс виртуальных равных.

Это верно, но компилятор этого не знает. Это будет определено во время выполнения.