Ответ 1
У меня нет опыта работы с С#, но насколько я знаю, это не может быть пустым внутри функции-члена (по крайней мере, это верно в С++ и Java, о языках, которые я знаю)
Начнем с того, что ваше утверждение ложно.
В С++ отправка метода на пустом приемнике - это поведение undefined, а поведение undefined означает, что все может случиться. "Anything" включает в себя передачу программы NULL
как this
и продолжение, как будто ничего не случилось. Конечно, глупо проверять, имеет ли значение this
значение null в С++, потому что проверка может быть правдой только если вы уже не знаете, что делает ваша программа, потому что ее поведение undefined.
Может ли this
быть пустым в Java, я понятия не имею.
Теперь, чтобы ответить на вопрос о С#. Предположим, что ==
не перегружен. Мы вернемся к этому вопросу позже.
Ваш метод написан на С#. Предположим, что он вызывается из программы С# с нулевым приемником. Компилятор С# оценивает, может ли получатель быть пустым; если он может быть пустым, то он гарантирует, что он генерирует код, который выполняет нулевую проверку перед вызовом метода. Поэтому эта проверка в этом сценарии бессмысленна. Это, конечно, вероятный сценарий 99,9999%.
Предположим, что он вызывается через Reflection, как в mike z answer. В этом случае это не язык С#, который выполняет вызов; скорее, кто-то сознательно злоупотребляет размышлениями.
Предположим, что он вызывается с другого языка. У нас есть виртуальный метод; если он вызывается с этого другого языка с виртуальной диспетчеризацией, тогда необходимо выполнить нулевую проверку, потому что, как еще мы можем узнать, что находится в виртуальном слоте? В этом случае он не может быть нулевым.
Но предположим, что он вызывается с другого языка, используя не виртуальную отправку. В этом случае другой язык не должен реализовывать функцию С# для проверки нулевого значения. Он может просто вызвать его и передать null.
Таким образом, существует несколько способов, в которых this
может быть NULL
в С#, но все они очень сильно отличаются от основного. Поэтому очень редко люди пишут код, как ваш профессор. Программисты С# идиоматически предполагают, что this
не NULL
и никогда не проверяет его.
Теперь, когда мы ушли с пути, давайте еще раз критиковать этот код.
public override bool Equals(object o) {
if (o == null)
return (this == null);
else {
return ((o is Person) && (this.dni == (o as Person).dni));
}
}
Во-первых, есть очевидная ошибка. Мы исходим из того, что this
может быть нулевым, ok, пусть работает с этим. Что останавливает this.dni
от исключения null reference reference. Если вы предположите, что this
может быть нулевым, то, по крайней мере, так последовательно! (В Coverity мы называем такую ситуацию "прямым дефектом нуля".)
Далее: мы переопределяем Equals
, а затем используем ==
внутри, предположительно, для обозначения ссылочного равенства. Этот путь - безумие! Теперь у нас есть ситуация, когда x.Equals(y)
может быть истинным, но x==y
может быть ложным! Это ужасно. Пожалуйста, не ходите туда. Если вы собираетесь переопределить Equals
, тогда перегрузите ==
в одно и то же время и реализуйте IEquatable<T>
, пока вы на нем.
(Теперь есть разумный аргумент в том, что безумие лежит в любом направлении, если ==
согласуется с Equals
со значением семантики, то personx == persony
может отличаться от (object)personx == (object)persony
, что кажется странным также. Вывод здесь состоит в том, что в С# довольно сложно совместить равенство.)
Более того: что, если ==
будет переопределено позже? Теперь Equals
вызывает переопределенный оператор ==
, когда автор кода явно хочет провести сравнительное сравнение. Это рецепт ошибок.
Мои рекомендации: (1) написать один статический метод, который делает правильную вещь, и (2) использовать ReferenceEquals
каждый раз, когда может быть какая-либо путаница в отношении того, что означает равенство:
private static bool Equals(Person x, Person y)
{
if (ReferenceEquals(x, y))
return true;
else if (ReferenceEquals(x, null))
return false;
else if (ReferenceEquals(y, null))
return false;
else
return x.dni == y.dni;
}
Это красиво охватывает каждый случай. Обратите внимание, что читателю ясно, когда подразумевается семантика ссылочной принадлежности. Также обратите внимание, что этот код очень легко помещает точки останова для каждой возможности для целей отладки. И, наконец, обратите внимание, что мы берем самые дешевые возможности на раннем этапе; если объекты являются ссылочными, то нам не нужно делать потенциально дорогое сравнение полей!
Теперь другие методы просты:
public static bool operator ==(Person x, Person y)
{
return Equals(x, y);
}
public static bool operator !=(Person x, Person y)
{
return !Equals(x, y);
}
public override bool Equals(object y)
{
return Equals(this, y as Person);
}
public bool Equals(Person y)
{
return Equals(this, y);
}
Обратите внимание на то, насколько элегантнее и понятнее мой путь, чем ваш профессор. И обратите внимание, что мой способ обрабатывает null this
, не сравнивая при этом this
с null.
Опять же: все это иллюстрирует, что достигнута компромиссная позиция, в которой возможны как значение, так и ссылочное равенство, и существует четыре способа (==
, !=
, object.Equals(object)
и IEquatable<T>.Equals(T)
)) для реализации равенства, очень сложна и запутанна даже без предположения, что this
может или не может быть NULL
.
Если эта тема вас интересует, я расскажу о немного более сложной проблеме в своем блоге на этой неделе: как реализовать сравнения в целом, включая неравенства.
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
Комментарии особенно интересны как критика того, как С# обрабатывает равенство.
Наконец: не забудьте переопределить GetHashCode
. Убедитесь, что вы сделали это правильно.