Почему это происходит?
Потому что в правилах языка сказано.
Вы предоставили оператору эту подпись:
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
, но я бы, вероятно, придерживался этого.