C# Wrappers – Should == Operator Compare Wrappers of the Same Object?

cnetoperators

I'm writing a wrapper for XML elements that allows a developer to easily parse attributes from the XML. The wrapper has no state other than the object being wrapped.

I am considering the following implementation (simplified for this example) which includes an overload for the == operator.

class XmlWrapper
{
    protected readonly XElement _element;

    public XmlWrapper(XElement element)
    {
        _element = element;
    }

    public string NameAttribute
    {
        get
        {
            //Get the value of the name attribute
        }
        set
        {
            //Set the value of the name attribute
        }
    }

    public override bool Equals(object other)
    {
        var o = other as XmlWrapper;
        if (o == null) return false;
        return _element.Equals(o._element);
    }

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

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;

        return lhs._element == rhs._element;
    }

    static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
    {
        return !(lhs == rhs);
    }
}

As I understand idiomatic c#, the == operator is for reference equality while the Equals() method is for value equality. But in this case, the "value" is just a reference to the object being wrapped. So I am not clear what is conventional or idiomatic for c#.

For example, in this code…

var underlyingElement = new XElement("Foo");
var a = new XmlWrapper(underlyingElement);
var b = new XmlWrapper(underlyingElement);

a.NameAttribute = "Hello";
b.NameAttribute = "World";

if (a == b)
{
    Console.WriteLine("The wrappers a and b are the same.");
}

….should the program output "The wrappers a and b are the same" ? Or would that be odd, i.e. violate the principal of least astonishment?

Best Answer

Since the reference to the wrapped XElement is immutable, there is no externally observable difference between two instances of XmlWrapper that wrap the same element, so it makes sense to overload == to reflect this fact.

Client code almost always cares about logical equality (which, by default, is implemented using reference equality for reference types). The fact that there are two instances on the heap is an implementation detail that clients shouldn't care about (and those that do will use Object.ReferenceEquals directly).