Почему компилятор не предупреждает об этом == null

Почему компилятор С# даже не жалуется на предупреждение об этом коде?

if (this == null)
{
   // ...
}

Очевидно, условие будет никогда.

Ответ 1

Потому что вы можете переопределить operator ==, чтобы вернуть true для этого случая.

public class Foo
{
    public void Test()
    {
        Console.WriteLine(this == null);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return true;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return true;
    }
}

Запуск new Foo().Test() будет печатать "True" на консоли.

Другой вопрос: почему компилятор не выдает предупреждение для ReferenceEquals(this, null)? В нижней части приведенной выше ссылки:

Общей ошибкой при перегрузках operator == является использование (a == b), (a == null) или (b == null) для проверки ссылочного равенства. Это приводит к вызову перегруженного operator ==, вызывая бесконечный цикл. Используйте ReferenceEquals или примените тип к объекту, чтобы избежать цикла.

На это может ответить ответ @Aaronaught. А также, почему вы должны делать (object)x == null или ReferenceEquals(x, null), не делая простой x == null, когда вы проверяете нулевые ссылки. Если, конечно, вы не уверены, что оператор == не перегружен.

Ответ 2

Ничего себе... Наверное, я был позорно ошибаюсь

Я не согласен. Я думаю, вы все еще хорошо разбираетесь.

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

И на самом деле, компилятор отслеживает, может ли данное выражение легально быть нулевым или нет, чтобы реализовать небольшую оптимизацию при вызове не виртуальных методов. Если у вас есть не виртуальный метод M, и вы говорите foo.M();, тогда компилятор генерирует это как "сделать виртуальный вызов M с приемником foo". Зачем? Потому что мы хотим выбросить, если foo имеет значение null, а виртуальный вызов всегда выполняет нулевую проверку на приемнике. Не виртуальный вызов не выполняется; мы должны были бы сгенерировать его как "проверить foo на null, а затем сделать не виртуальный вызов на M", что является более длинным, медленным, более раздражающим кодом.

Теперь, если мы можем уйти, не выполняя нулевую проверку, мы делаем. Если вы скажете this.M() или (new Foo()).M(), то мы НЕ создадим виртуальный вызов. Мы генерируем не виртуальный вызов без нулевой проверки, потому что знаем, что он не может быть нулевым.

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

Тогда возникает вопрос: "если компилятор знает, что конкретное сравнение никогда не будет успешным, почему бы не создать предупреждение для него?"

И ответ "иногда мы делаем, а иногда и не делаем".

Мы делаем в этой ситуации:

int x = 123;
if (x == null) ...

Существует оператор равенства, определенный на двух допустимых значениях int. x преобразуется в значение null. null может быть конвертирован в nullable int. Таким образом, оператор равенства действителен и, следовательно, используется и, конечно, всегда неверен. Компилятор дает предупреждение о том, что выражение всегда неверно.

Однако из-за ошибки, которую мы случайно ввели в С# 3, этот код НЕ производит это предупреждение:

Guid x = whatever;
if (x == null) ...

Такая же сделка. Оператор равенства nullable guid равен действию, но предупреждение подавляется. Я не уверен, исправлена ​​ли эта ошибка для С# 4 или нет. Если нет, мы надеемся, что мы включим его в пакет обновления.

Что касается "if (this == null)", я не знаю, почему мы не предупреждаем об этом. Это, безусловно, кажется хорошим кандидатом на предупреждение. Наиболее вероятным объяснением является следовать этому логическому силлогизму:

  • предупреждения - это функции компилятора.
  • Функции компилятора должны быть (1) считаться, (2) разработаны, (3) реализованы, (4) протестированы, (5) документально и (6) отправлены вам, прежде чем вы сможете воспользоваться этой функцией.
  • Никто не сделал ни одного из этих шести необходимых вещей для этой функции; мы не можем отправлять функции, о которых мы никогда не думали.
  • поэтому нет такой функции.

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

Еще одна причина не давать предупреждения здесь - это то, что мы пытаемся дать предупреждения о коде, который, вероятно, будет напечатан случайно и почти наверняка неправилен по неочевидной причине. "this == null" вряд ли будет введен случайно, и, хотя это почти наверняка неправильно, как вы заметили в формулировке вашего вопроса, это, очевидно, неверно.

Сравните это с нашим "guid == null" - это может произойти случайно, потому что разработчик может случайно подумать, что Guid является ссылочным типом. Гиды обычно передаются по ссылке на С++, так что это непростая ошибка. Код почти наверняка ошибочен, но неправильно это неочевидным образом. Так что это хороший кандидат на предупреждение. (Вот почему так жаль, что это предупреждение в С# 2, но не С# 3, из-за ошибки, которую мы ввели.)

Ответ 3

Собственно, условие действительно может быть удовлетворено, по крайней мере, в Visual Studio 2008. Они исправили это поведение в VS 2010, но совершенно немыслимо, что может быть другой способ создать такое условие.

(tl; dr version - в С# 3.5, он легален для ссылки this на анонимную функцию, переданную как аргумент конструктора, но если вы действительно попытаетесь ее использовать, вы обнаружите, что this есть null.)

Ответ 4

Хотя код ниже - ублюдок, он все еще все С#. a если вы вызываете Bar, вы получите InvalidOperationException с сообщением null.

public class Foo
{
    static Action squareIt;
    static Foo() {
        var method = new DynamicMethod(
                                       "TryItForReal",
                                       typeof(void),
                                       Type.EmptyTypes,
                                       typeof(Foo).Module);
        ILGenerator il = method.GetILGenerator();
        il.Emit(OpCodes.Ldnull);
        il.Emit(OpCodes.Call, typeof(Foo).GetMethod("tryit"));
        il.Emit(OpCodes.Ret);
        squareIt = (Action)method.CreateDelegate(typeof(Action));
    }

    public void tryit()
    {
        if (this == null) {
            throw new InvalidOperationException("Was null");
        }
    }

    public void Bar() {
        squareIt();
    }
}

Ответ 5

Это также соответствует другим предупреждениям, которые С# делает (или не делает):

if(true)
or 
if(1 == 1)

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