Почему компилятор С# даже не жалуется на предупреждение об этом коде?
if (this == null)
{
// ...
}
Очевидно, условие будет никогда.
Почему компилятор С# даже не жалуется на предупреждение об этом коде?
if (this == null)
{
// ...
}
Очевидно, условие будет никогда.
Потому что вы можете переопределить 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
, когда вы проверяете нулевые ссылки. Если, конечно, вы не уверены, что оператор ==
не перегружен.
Ничего себе... Наверное, я был позорно ошибаюсь
Я не согласен. Я думаю, вы все еще хорошо разбираетесь.
Компилятор знает, будет ли сравнение идти к пользовательскому оператору сравнения или нет, а компилятор знает, что если он этого не делает, то '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)", я не знаю, почему мы не предупреждаем об этом. Это, безусловно, кажется хорошим кандидатом на предупреждение. Наиболее вероятным объяснением является следовать этому логическому силлогизму:
Есть бесконечно много функций компилятора, о которых мы еще не думали; мы не реализовали ни одного из них.
Еще одна причина не давать предупреждения здесь - это то, что мы пытаемся дать предупреждения о коде, который, вероятно, будет напечатан случайно и почти наверняка неправилен по неочевидной причине. "this == null" вряд ли будет введен случайно, и, хотя это почти наверняка неправильно, как вы заметили в формулировке вашего вопроса, это, очевидно, неверно.
Сравните это с нашим "guid == null" - это может произойти случайно, потому что разработчик может случайно подумать, что Guid является ссылочным типом. Гиды обычно передаются по ссылке на С++, так что это непростая ошибка. Код почти наверняка ошибочен, но неправильно это неочевидным образом. Так что это хороший кандидат на предупреждение. (Вот почему так жаль, что это предупреждение в С# 2, но не С# 3, из-за ошибки, которую мы ввели.)
Собственно, условие действительно может быть удовлетворено, по крайней мере, в Visual Studio 2008. Они исправили это поведение в VS 2010, но совершенно немыслимо, что может быть другой способ создать такое условие.
(tl; dr version - в С# 3.5, он легален для ссылки this
на анонимную функцию, переданную как аргумент конструктора, но если вы действительно попытаетесь ее использовать, вы обнаружите, что this
есть null
.)
Хотя код ниже - ублюдок, он все еще все С#. 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();
}
}
Это также соответствует другим предупреждениям, которые С# делает (или не делает):
if(true)
or
if(1 == 1)
Они также всегда будут иметь одинаковый результат независимо от того, что.