Почему компилятор по крайней мере не предупреждает об этом == ноль - PullRequest
21 голосов
/ 17 марта 2010

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

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

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

Ответы [ 5 ]

31 голосов
/ 17 марта 2010

Поскольку вы можете переопределить 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, когда вы проверяете нулевые ссылки. Если, конечно, вы не уверены, что оператор == не перегружен.

24 голосов
/ 20 марта 2010

Ух ты ... Думаю, я был постыдно неправ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 17 марта 2010

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

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

3 голосов
/ 28 июля 2011

Хотя приведенный ниже код является мерзавцем, он все еще на C #. Если вы позвоните в 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();
    }
}
2 голосов
/ 17 марта 2010

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

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

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

...