Reputation: 52210
I've written a simple class that wraps an XElement. I want to pass through equality operations to the wrapped instance. So I wrote this:
public class XmlWrapper
{
protected readonly XElement _element;
public XmlWrapper(XElement element)
{
_element = element;
}
static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
return lhs._element.Equals(rhs._element);
}
static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
{
return !(lhs == rhs);
}
}
This seems straightforward, but actually it throws an exception for a very simple null check:
XmlWrapper wrapper = new XmlWrapper(someElement);
XmlWrapper nullWrapper = null;
if (wrapper != nullWrapper) Console.WriteLine("Wrapper is not null"); //This line throws
It's because the equality operator is receiving null for one of its arguments and therefore can't retrieve the wrapped XElement. So the obvious thought is to add a null check, like this:
static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
if (lhs == null && rhs == null) return true;
if (lhs == null || rhs == null) return false;
return lhs._element.Equals(rhs._element);
}
However, this results infinite recursion, because the ==
operator calls the ==
operator again.
If this were any other kind of method, I'd just call the base, but that doesn't work with an operator, e.g. you can't write
if (lhs base.== rhs)
So how do I get around this? Is there some way to call the base ==
operator from within the overloaded operator body? Or some other way to perform the null check without using ==
?
Here is the code on DotNetFiddle.
Upvotes: 4
Views: 383
Reputation: 247018
This should provide the desired behavior.
Also note that if overriding equality you need to override GetHashCode
public class XmlWrapper : IEquatable<XmlWrapper> {
protected readonly XElement _element;
public XmlWrapper(XElement element) {
_element = element ?? throw new ArgumentNullException(nameof(element));
}
static public bool operator ==(XmlWrapper lhs, XmlWrapper rhs) {
return Equals(lhs, rhs);
}
static public bool operator !=(XmlWrapper lhs, XmlWrapper rhs) {
return !Equals(lhs, rhs);
}
public override string ToString() {
return _element != null ? _element.ToString() : this.GetType().FullName;
}
public override int GetHashCode() {
return _element.GetHashCode();
}
public override bool Equals(object obj) {
return obj is XmlWrapper other && Equals(other);
}
public bool Equals(XmlWrapper other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return _element.Equals(other._element);
}
}
It is worth noting this implementation is specific to reference types. Since XmlWrapper
is a reference type.
Upvotes: 2
Reputation: 37020
You can shorten the code down to one line using the ternary operator:
static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
return lhs is null ? rhs is null : !(rhs is null) && lhs._element == rhs._element;
}
Which says, "if lhs is null, return true if rhs is null or false if rhs is not null. Otherwise (if lhs is not null), return true if rhs is not null and their elements are equals, else return false."
Upvotes: 1
Reputation: 174
You can apply Elvis operator and null-coalescing operator as well to make it working.
static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
if (lhs?._element == null && rhs?._element == null) return true;
return lhs?._element?.Equals(rhs?._element) ?? false;
}
Upvotes: 0
Reputation: 1057
public static bool operator == (XmlWrapper lhs, XmlWrapper rhs)
{
if (Object.ReferenceEquals(lhs, null) && Object.ReferenceEquals(rhs, null))
{
return true;
}
if (Object.ReferenceEquals(lhs, null) || Object.ReferenceEquals(rhs, null))
{
return false;
}
return lhs._element.Equals(rhs._element);
}
Upvotes: 4