Как перезаписать оператор! =?

Пытаюсь переписать оператор! =:

public class Box
{
    public Box()
    {
    }

    public Box(double height, double width)
    {
        Height = height;
        Width = width;
    }

    public double Height { get; set; }
    public double Width { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            return (Height.GetHashCode() * 397) ^ Width.GetHashCode();
        }
    }
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        return obj.GetType() == GetType() && Equals((Box)obj);
    }

    protected bool Equals(Box other)
    {
        return Math.Abs(Height - other.Height) + Math.Abs(Width - other.Width) < 0.001;
    }
    public static bool operator ==(Box left, Box right)
    {
        if (ReferenceEquals(null, left))
            return false;

        if (ReferenceEquals(null, right))
            return false;

        return left.Equals(right);
    }

    public static bool operator !=(Box left, Box right)
    {
        var t = !(left == right);
        return t;
    }
}

public class BetterBox:Box{

}

И попробуйте использовать оператор! =

var box = new Box();
var betterBox = box as BetterBox;
if(betterBox!=null){
    --do-something
}

В этом случае! = Вернуть true и ввести код в if. Что здесь не так? Почему это хеппенс?
На mdsn я вижу тот же код: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/336aedhh%28v%3dvs.100%29

Это то же самое, что и вызов if (!(box == null)), и ваш оператор ==, если ссылка на любой из сторон равна null, он возвращает false, что становится if (!(false)), то есть if (true) ...

Ron Beyer 13.09.2018 21:07

@RonBeyer да != вернуть true. Но я видел симуляционный код на mdsn и stackoverflow. Я что-то упускаю?

Kliver Max 13.09.2018 21:12

@KliverMax - Вы, наверное, пропустили самую первую проверку, которая обычно должна быть при каждой проверке равенства: if (ReferenceEquals(left, right)) { return true; }

Corak 13.09.2018 21:18

@KliverMax Возможно, вы упустили из виду, что пример, на который вы ссылаетесь в документах MS, предназначен для типа значения, а не для ссылочного типа, поэтому правила немного отличаются. То, что говорит Корак, верно для ссылочных типов, вам нужна эта проверка, и она должна быть первой для ссылочных типов.

Ron Beyer 13.09.2018 21:23

@Corak return ReferenceEquals(left, right);

Lews Therin 13.09.2018 21:25

Кроме того, в более новых версиях вы можете использовать if(thing is null) и if (!(thing is null)) соответственно.

Corak 13.09.2018 21:25

@Corak Я пропустил эту проверку, потому что не уверен, что понимаю, что он делает. Но попробую прямо сейчас.

Kliver Max 13.09.2018 21:27

@LewsTherin - Не обязательно. Два экземпляра можно считать равными, даже если они не равны по ссылкам. Рассмотрим: class X { public int Y { get;set; } } и var a = new X { Y = 1 }; var b = new X { Y = 1 }; - теперь a и b не будут равными по ссылкам, но, вероятно, предназначены для того, чтобы их считали равными.

Corak 13.09.2018 21:29

@Corak Я думаю, вы случайно ответили кому-то другому. Все, что я сделал, это рефакторинг вашего кода if (ReferenceEquals(left, right)) { return true; } до return ReferenceEquals(left, right);. Эти утверждения делают то же самое.

Lews Therin 13.09.2018 21:33

@KliverMax - Делает то, что говорит. Он возвращает true, если две переменные ссылаются на одно и то же (в основном: указывают на один и тот же адрес в памяти). Таким образом, если betterBox - это NullReference, а null - это NullReference, тогда они равны ссылкам, и betterBox == null вернет true, и, следовательно, betterBox != null вернет false.

Corak 13.09.2018 21:33

@LewsTherin - нет. Я пытался объяснить, что ваш рефакторинг зайдет слишком далеко. Я сказал, что проверка должна быть самой проверкой первый. Но это ни в коем случае не должна быть проверка Только. - в моем примере я ожидал, что operator == будет выглядеть примерно как { if (ReferenceEquals(left, right)) { return true; } if (ReferenceEquals(null, left) || ReferenceEquals(null, right)) { return false; } return left.Y == right.Y; }

Corak 13.09.2018 21:37

@Corak Эта проверка мне помогает. Как насчет случая, когда у меня есть 2 коробки с одинаковыми width и height? ReferenceEquals(left,right) вернет false?

Kliver Max 13.09.2018 21:39

@KliverMax - именно так! Вот почему нужны проверки дополнительный.

Corak 13.09.2018 21:40

@Corak Я идиот, не обращайте внимания на мои комментарии.

Lews Therin 13.09.2018 22:16

Для проверки типов используйте оператор is вместо GetType(). Первый - это время компиляции, а позднее - время выполнения. Используйте if(obj is Box box) { return Equals(box); } (я думаю, требуется .NET 4.7.1).

John Alexiou 13.09.2018 22:24

Этот блок должен быть реализован как неизменяемая структура. Нынешний дизайн будет ужасен в использовании. И ваш Equals () не транзитивен.

bommelding 04.10.2018 08:58
0
16
161
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваша реализация оператора == неверна. При проверке нулевого значения необходимо учитывать оба операнда. В настоящее время вы возвращаете false, если left равен нулю, игнорируя значение операнда right. Если они оба null, он должен вернуть true.

public static bool operator ==(Box left, Box right)
{
    var isLeftNull = ReferenceEquals(null, left);
    var isRightNull = ReferenceEquals(null, right);

    if (isLeftNull && isRightNull)
    {
        return true;
    }

    if (isLeftNull || isRightNull)
    {
        return false;
    }

    return left.Equals(right);
}

Это правильно, что эта дополнительная проверка совпадает с комментарием @Corac if (ReferenceEquals(left, right)) { return true; }?

Kliver Max 13.09.2018 21:44

@KliverMax этого недостаточно, вы должны убедиться, что ни один из операндов не равен нулю, прежде чем вызывать equals.

Selman Genç 13.09.2018 21:46

Если right - это null, тогда left.Equals(right) должен возвращать false (всегда).

John Alexiou 13.09.2018 21:46

@ ja72 - действительно, но это означает, что leftне должен имеет значение null (если это так, вызов left.Equals вызовет исключение NullReferenceException).

Corak 13.09.2018 21:48

@KliverMax - да, они логически эквивалентны. - опять же, мое предложение было просто поставить if (ReferenceEquals(left, right)) { return true; }до ваши другие уже существующие проверки.

Corak 13.09.2018 21:49
Ответ принят как подходящий

Это полная реализация Box с использованием проверок равенства (реализация IEquatable<Box>). Ниже приведены результаты тестирования:

       a        b     a==b     a!=b    a.Equals(b)    b.Equals(a)
    null     null     True    False                              
 [30×10]     null    False     True          False               
 [30×10]  [30×10]     True    False           True           True
    null  [30×10]    False     True                         False

Я реализовал Equals(Box), Equals(object), GetHashCode(), operator ==, operator != и ToString().

public class Box : IEquatable<Box>
{
    // Place values in constants
    public const double SizeTolerance = 0.001;

    public double Width { get; set; }
    public double Height { get; set; }

    public static bool operator ==(Box left, Box right)
    {
        if(!ReferenceEquals(left, null))
        {
            // consider that left.Equals(null) should return false
            return left.Equals(right);
        }
        return ReferenceEquals(left, right);
    }
    public static bool operator !=(Box left, Box right)
    {
        return !(left==right);
    }

    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(Box)</code></returns>
    public override bool Equals(object obj)
    {
        if(obj is Box other)
        {
            return Equals(other);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="Box"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="Box"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public bool Equals(Box other)
    {
        if(ReferenceEquals(other, null))
        {
            return false;
        }
        return Math.Abs(Width-other.Width)<SizeTolerance
            && Math.Abs(Height-other.Height)<SizeTolerance;
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="Box"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            int hc = 17;
            hc = 23*hc + Width.GetHashCode();
            hc = 23*hc + Height.GetHashCode();
            return hc;
        }
    }

    #endregion

    public override string ToString()
    {
        return $"[{Width}×{Height}]";
    }
}

и код для его проверки:

    static void Main(string[] args)
    {
        Debug.WriteLine($"{"a",8} {"b",8} {"a==b",8} {"a!=b",8} {"a.Equals(b)",14} {"b.Equals(a)",14}");
        Box a = null;
        Box b = null;
        Debug.WriteLine($"{a?.ToString()??"null",8} {b?.ToString()??"null",8} {a==b,8} {a!=b,8} {a?.Equals(b),14} {b?.Equals(a),14}");
        a = new Box() { Height = 10, Width = 30 };
        Debug.WriteLine($"{a?.ToString()??"null",8} {b?.ToString()??"null",8} {a==b,8} {a!=b,8} {a?.Equals(b),14} {b?.Equals(a),14}");
        b = new Box() { Height = 10, Width = 30 };
        Debug.WriteLine($"{a?.ToString()??"null",8} {b?.ToString()??"null",8} {a==b,8} {a!=b,8} {a?.Equals(b),14} {b?.Equals(a),14}");
        a = null;
        Debug.WriteLine($"{a?.ToString()??"null",8} {b?.ToString()??"null",8} {a==b,8} {a!=b,8} {a?.Equals(b),14} {b?.Equals(a),14}");
    }
}

Другие вопросы по теме