Почему исключение stackoverflow выбрасывается в этом определении оператора? - PullRequest
8 голосов
/ 11 марта 2012

Пожалуйста, смотрите мой комментарий в коде ниже.Как мне проверить, чтобы параметр был null?Похоже, null преобразуется в Foo, что по существу делает рекурсивный вызов оператора ==.Почему это происходит?

public class Foo
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (f1 == null) //This throw a StackOverflowException
            return f2 == null;
        if (f2 == null)
            return f1 == null;
        else
            return f1.Equals((object)f2);
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override bool Equals(object obj)
    {
        Foo f = obj as Foo;
        if (f == (Foo)null)
            return false;

        return false;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

Ответы [ 2 ]

26 голосов
/ 11 марта 2012

Почему это происходит?

Потому что в правилах языка сказано.

Вы предоставили оператору эту подпись:

public static bool operator ==(Foo f1, Foo f2)

и затем - где бы это ни было в коде - у вас есть это выражение:

f1 == null

, где f1 имеет тип времени компиляции Foo. Теперь null также неявно конвертируется в Foo, так почему же не будет использовать ваш оператор? И если у вас есть первая строка вашего оператора, безоговорочно вызывающая себя, вы должны ожидать переполнения стека ...

Чтобы это не произошло, вам нужно одно из двух изменений языка:

  • Язык должен иметь особый случай, что имел в виду ==, когда он используется в объявлении для ==. Ик.
  • Язык должен решить, что любое выражение == с одним операндом, равным null всегда , означает эталонное сравнение.

Ни один из них не особенно хорош, ИМО. Однако избежать этого просто, избежать избыточности и добавить оптимизацию:

public static bool operator ==(Foo f1, Foo f2)
{
    if (object.ReferenceEquals(f1, f2))
    {
        return true;
    }
    if (object.ReferenceEquals(f1, null) ||
        object.ReferenceEquals(f2, null))
    {
        return false;
    }
    return f1.Equals(f2);
}

Однако вам , а затем необходимо исправить свой метод Equals, потому что это в конечном итоге приведет к обратному вызову вашего ==, что приведет к очередному переполнению стека . Вы никогда не на самом деле заканчивали тем, что говорили, как вы хотите, чтобы было определено равенство ...

У меня обычно было бы что-то вроде этого:

// Where possible, define equality on sealed types.
// It gets messier otherwise...
public sealed class Foo : IEquatable<Foo>
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (object.ReferenceEquals(f1, f2))
        {
            return true;
        }
        if (object.ReferenceEquals(f1, null) ||
            object.ReferenceEquals(f2, null))
        {
            return false;
        }

        // Perform actual equality check here
    }

    public override bool Equals(object other)
    {
        return this == (other as Foo);
    }

    public bool Equals(Foo other)
    {
        return this == other;
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override int GetHashCode()
    {
        // Compute hash code here
    }
}

Обратите внимание, что это позволяет вам проверять недействительность только в одном месте. Чтобы избежать избыточного сравнения f1 для нуля, когда он вызывается с помощью метода экземпляра Equals для начала, вы могли бы делегировать от == до Equals после проверки на ничтожность f1, но я бы, вероятно, придерживался этого.

0 голосов
/ 12 сентября 2018

Немного старая тема, но я дошел до этой страницы, так что это может помочь некоторым людям. Чтобы сохранить то же поведение, но без переполнения стека, я бы переписал его следующим образом:

public class Foo
{
    public static bool operator ==(Foo f1, Foo f2)
    {
        if (f1 is null) 
            return f2 is null;
        if (f2 is null)
            return false;
        else
            return f1.Equals((object)f2);
    }

    public static bool operator !=(Foo f1, Foo f2)
    {
        return !(f1 == f2);
    }

    public override bool Equals(object obj)
    {
        Foo f = obj as Foo;
        if(f is null) return false;
        return f == this;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}
...