Подтвердить что ты не робот

Возможно ли, чтобы ключевое слово 'this' равнялось нулю?

В одном примере мой профессор применил Equals следующим образом:

public class Person {
    private string dni;

    // ...

    public override bool Equals(object o) {
        if (o == null)
            return (this == null);
        else {
            return ((o is Person) && (this.dni == (o as Person).dni));
        }
    }
}

У меня нет опыта работы с С#, но насколько я знаю, this не может быть null внутри функции-члена (по крайней мере, это верно в С++ и Java, о языках, которые я знаю), поэтому if кажется мне странным.

Я прав или есть какой-либо компонент в С# Я не знаю, из чего делает тест this == null необходимым?

4b9b3361

Ответ 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. Убедитесь, что вы сделали это правильно.

Ответ 2

Да, в некоторых случаях. Наиболее распространенный случай: если вы вызываете метод экземпляра, используя делегат, созданный отражением, и передаете нулевой приемник. Это напечатает "True":

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

public class Program
{
    public static void Main(string[] args)
    {
        object t = new Test();
        var methodInfo = t.GetType().GetMethod("Method");
        var a = (Action)Delegate.CreateDelegate(typeof(Action), null, methodInfo);
        a();
    }
}

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

Ответ 3

Один из способов this мог бы == null, если вы практиковали какую-то черную магию, перегружая оператор ==. Например, если кто-то решит, что наличие пустой или пустой строки dni эквивалентно нулевому человеку:

public static bool operator ==(Person a, Person b)
{
    if(String.IsNullOrEmpty(a.dni) && (object)b == null)
        return true;

    if(String.IsNullOrEmpty(b.dni) && (object)a == null)
        return true;

    return Object.ReferenceEquals(a, b);
}

Обратите внимание, что это, вероятно, действительно плохая идея.

Ответ 4

Да, это возможно и на самом деле не совсем маловероятно. Язык С# дает отличную гарантию того, что ни один метод не может быть вызван нулевой ссылкой на объект класса, он генерирует код, который делает нулевую проверку на сайте вызова. Который, конечно, избегает большого количества царапин на голове, исключение NullReferenceException внутри метода может быть довольно сложно отладить без этой проверки.

Эта проверка, однако, специфична для С#, но не все языки .NET реализуют ее. Язык С++/CLI не используется, например. Вы можете увидеть это сами, создав решение с помощью проекта режима консоли С++ CLR и библиотеки классов С#. Добавьте ссылку в проект CLR в проект С#.

Код С#:

using System;

namespace ClassLibrary1 {
    public class Class1 {
        public void NullTest() {
            if (this == null) Console.WriteLine("It null");
        }
    }
}

Код С++/CLI:

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args) {
    ClassLibrary1::Class1^ obj = nullptr;
    obj->NullTest();
    Console::ReadLine();
    return 0;
}

Вывод:

Это значение null

Это поведение иначе undefined или незаконное. Спецификация CLI не верботна. И пока метод не имеет доступа к каким-либо членам экземпляра, тогда ничего не происходит. CLR обрабатывает это также, исключение NullReferenceException также генерируется для указателей, которые не являются нулевыми. Подобно тому, как NullTest() обращается к полю, которое не является первым полем в классе.