I am having problems getting the desired behavior out of these few classes and interfaces.
Here is my problem,
//Inside a Unit Test that has access to internal methods and properties
INode firstNode, secondNode;
INodeId id = new NodeId (4);
first = new Node (id, "node");
second = new Node (id, "node");
Assert.IsTrue (first == second);
The assert above is failing because it seems to be going to the object class's equals method instead of the overloaded operator in the Node and NodeId classes.
If you have any suggestions on how I can get the desired behavior, that would be awesome.
Here is part of the Framework I am working on:
public interface IIdentifier<T> where T : class
{
TKeyDataType GetKey<TKeyDataType> ();
bool Equals (IIdentifier<T> obj;
}
public interface INode
{
string name
{
get;
}
INodeId id
{
get;
}
}
public interface INodeId : IIdentifier<INode>
{
}
public class Node : INode
{
internal Node(INodeId id, string name)
{
//Work
}
public static bool operator == (Node n1, Node n2)
{
return n1.equals(n2);
}
public static bool operator != (Node n1, Node n2)
{
return !n1.equals(n2);
}
public bool Equals (INode node)
{
return this.name == node.name &&
this.id = node.id;
}
#region INode Properties
}
public class NodeId : INodeId
{
internal NodeId(int id)
{
//Work
}
public static bool operator == (NodeId n1, NodeId n2)
{
return n1.equals(n2);
}
public static bool operator != (NodeId n1, NodeId n2)
{
return !n1.equals(n2);
}
public override bool Equals (object obj)
{
return this.Equals ((IIdentifier<INode>) obj);
}
public bool Equals (IIdentifier<INode> obj)
{
return obj.GetKey<int>() == this.GetKey<int>();
}
public TKeyDataType GetKey<TKeyDataType> ()
{
return (TKeyDataType) Convert.ChangeType (
m_id,
typeof (TKeyDataType),
CultureInfo.InvariantCulture);
}
private int m_id;
}
Operator overloads are resolved at compile time based on the declared types of the operands, not on the actual type of the objects at runtime. An alternate way of saying this is that operator overloads aren't virtual. So the comparison that you're doing above is INode.operator==, not Node.operator==. Since INode.operator== isn't defined, the overload resolves to Object.operator==, which just does reference comparison.
There is no really good way around this. The most correct thing to do is to use Equals() rather than == anywhere the operands might be objects. If you really, really need a fake virtual operator overload, you should define operator == in the root base class that your objects inherit from, and have that overload call Equals. Note, however, that this won't work for interfaces, which is what you have.
I think you might need to override Equals(object) in Node like you did in NodeId. So:
public override bool Equals (object obj)
{
if (obj is Node)
{
return this.Equals(obj as Node);
}
return false;
}
// your code (modified to take a Node instead of an INode)
public bool Equals (Node node)
{
return this.name == node.name &&
this.id = node.id;
}
it's using the == from object because firstNode and secondNode aren't of type Node, they're of type INode. The compiler isn't recognizing the underlying types.
Since you can't overload an operator in an interface, your best bet is probably to rewrite the code so that it doesn't use the INode interface.
Related
Given the following,
public record Foo(int Id)
{
public virtual bool Equals(Foo? foo)
{
Console.WriteLine($"foo {(foo is null ? "IS" : "is NOT")} null");
return foo is not null && Id == foo.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}
public record FooSummary(int Id, string Summary) : Foo(Id);
public record FooDetail(int Id, string Detail) : Foo(Id);
var summary = new FooSummary(1, "Summary");
var detail = new FooDetail(1, "Detail");
Console.WriteLine(detail == summary);
// Output:
// foo IS null
// false
Is it possible to customize record equality is such a way that detail == summary is true?
In a class I could override Equals(object obj), but in a record that results in a compilation error (CS0111)
Edit
I've accepted #StriplingWarrior's answer as it technically answers my question, but as he and others have explained: this is a bad idea. This was definitely a case of the XY problem and trying to be a little too clever for my own good.
Technically, you can, by overriding the == operators.
public record Foo(int Id)
{
public virtual bool Equals(Foo? foo)
{
return foo is not null && Id == foo.Id;
}
public override int GetHashCode() => Id.GetHashCode();
}
public record FooSummary(int Id, string Summary) : Foo(Id)
{
public bool Equals(FooDetail? other)
{
return base.Equals((Foo?)other);
}
public override int GetHashCode() => base.GetHashCode();
}
public record FooDetail(int Id, string Detail) : Foo(Id)
{
public bool Equals(FooSummary? other)
{
return base.Equals((Foo?)other);
}
public static bool operator ==(FooDetail? left, FooSummary? right)
=> (object?)left == right || (left?.Equals(right) ?? false);
public static bool operator !=(FooDetail? left, FooSummary? right)
=> !(left == right);
public static bool operator ==(FooSummary? left, FooDetail? right)
=> (object?)left == right || (left?.Equals(right) ?? false);
public static bool operator !=(FooSummary? left, FooDetail? right)
=> !(left == right);
public override int GetHashCode() => base.GetHashCode();
}
That doesn't mean it's a good idea. You'll get unexpected behaviors when your types are explicitly cast as the specific types you're trying to compare.
In my experience, when people are tempted to override equality operators, that's usually the wrong tool for what they're trying to accomplish.
C# 9 Documentation seems pretty clear:
Two variables of a record type are equal if the record type definitions are identical, and if for every field, the values in both records are equal.
My read of this is that it leaves no room to customize equality for a record type.
Given a parent record type:
public record Foo(string Value);
And two record sub-classes Bar and Bee I wonder if it is possible to implement Equals in the base class so as instances of Foo, Bar or Bee are all considered equal based on Value (both with Equals and ==).
I tried the following after digesting https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records, but it didn't quite work:
public record Foo(string Value)
{
public virtual bool Equals(Foo? other)
{
return other != null && this.Value == other.Value;
}
public override int GetHashCode() => this.Value.GetHashCode();
}
public record Bar(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
public record Bee(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
[Test]
public void TestFooBar()
{
Assert.That(new Foo("foo") == new Bar("foo"), Is.True); // Passes
Assert.That(new Bar("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestFooBee()
{
Assert.That(new Foo("foo") == new Bee("foo"), Is.True); // Passes
Assert.That(new Bee("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestBarBee()
{
Assert.That(new Bar("foo") == new Bee("foo"), Is.True); // Fails!
Assert.That(new Bee("foo") == new Bar("foo"), Is.True); // Fails!
}
This question is specific to record types. I don't need an example with classes (I know that already).
When I look at sharplab.io, I see the following implementation for Bar:
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Bar);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public sealed override bool Equals(Foo other)
{
return Equals((object)other);
}
And in Foo:
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator ==(Foo left, Foo right)
{
if ((object)left != right)
{
if ((object)left != null)
{
return left.Equals(right);
}
return false;
}
return true;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Foo);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(Foo other)
{
if (other != null)
{
return Value == other.Value;
}
return false;
}
So naturally new Bar("foo") == new Bee("foo") would end-up calling Bar.Equals(Foo?), which is synthetic and relies on Bar.Equals(object) which will cast to Bar or return null, and because a Bee is not a Bar it ends-up comparing null.
It doesn't seem possible to override some of the synthetic Equals methods so it seems I cannot escape this behavior.
Or, can I?
NOTE: I'm on .NET SDK 6.0.
This is the stack trace when calling new Bar("foo") == new Foo("foo"):
at Foo.Equals(Foo other)
at Bar.Equals(Bar other)
at Bar.Equals(Object obj)
at Bar.Equals(Foo other)
at Foo.op_Equality(Foo r1, Foo r2)
According to the draft spec, you cannot declare any of those methods explicitly (i.e. do not have control over them) except Foo.Equals(Foo) and Bar.Equals(Bar), at which point other has already been (unsuccessfully) casted to Bar, in Bar.Equals(Object).
Though it is not totally impossible - you can declare the operators ==(Bar, Foo), !=(Bar, Foo) etc manually, and let operator overload resolution pick your operator, instead of the ==(Foo, Foo) one, which you have no control over. But this is quite tedious to do for all the types, and you'd still have the problem of Bar.Equals(Foo) not working the way you want. :(
EqualityContract is rather irrelevant. It is only checked in the generated Foo.Equals(Foo),
The synthesized Equals(R?) returns true if and only if each of the following are true:
[...]
If there is a base record type, the value of base.Equals(other) (a non-virtual call to public virtual bool Equals(Base? other)); otherwise the value of EqualityContract == other.EqualityContract.
but at that point other is null already! Not to mention that you wrote your own Foo.Equals(Foo), so the EqualityContracts are not used at all.
Why does this program print "not added" while I think it should print "added"?
using System;
using System.Collections.Generic;
class Element
{
public int id;
public Element(int id)
{
this.id = id;
}
public static implicit operator Element(int d)
{
Element ret = new Element(d);
return ret;
}
public static bool operator ==(Element e1, Element e2)
{
return (e1.id == e2.id);
}
public static bool operator !=(Element e1, Element e2)
{
return !(e1.id == e2.id);
}
}
class MainClass
{
public static void Main(string[] args)
{
List<Element> element = new List<Element>();
element.Add(2);
if(element.Contains(2))
Console.WriteLine("added");
else
Console.WriteLine("not added");
}
}
The Contains method does not use the == operator. What is the problem?
The Contains method does not use the == operator
No - it uses Equals, which you haven't overridden... so you're getting the default behaviour of Equals, which is to check for reference identity instead. You should override Equals(object) and GetHashCode to be consistent with each other - and for sanity's sake, consistent with your == overload too.
I'd also recommend implementing IEquatable<Element>, which List<Element> will use in preference to Equals(object), as EqualityComparer<T>.Default picks it up appropriately.
Oh, and your operator overloads should handle null references, too.
I'd also strongly recommend using private fields instead of public ones, and making your type immutable - seal it and make id readonly. Implementing equality for mutable types can lead to odd situations. For example:
Dictionary<Element, string> dictionary = new Dictionary<Element, string>();
Element x = new Element(10);
dictionary[x] = "foo";
x.id = 100;
Console.WriteLine(dictionary[x]); // No such element!
This would happen because the hash code would change (at least under most implementations), so the hash table underlying the dictionary wouldn't be able to find even a reference to the same object that's already in there.
So your class would look something like this:
internal sealed class Element : IEquatable<Element>
{
private readonly int id;
public int Id { get { return id; } }
public Element(int id)
{
this.id = id;
}
public static implicit operator Element(int d)
{
return new Element(d);
}
public static bool operator ==(Element e1, Element e2)
{
if (object.ReferenceEquals(e1, e2))
{
return true;
}
if (object.ReferenceEquals(e1, null) ||
object.ReferenceEquals(e2, null))
{
return false;
}
return e1.id == e2.id;
}
public static bool operator !=(Element e1, Element e2)
{
// Delegate...
return !(e1 == e2);
}
public bool Equals(Element other)
{
return this == other;
}
public override int GetHashCode()
{
return id;
}
public override bool Equals(object obj)
{
// Delegate...
return Equals(obj as Element);
}
}
(I'm not sure about the merit of the implicit conversion, by the way - I typically stay away from those, myself.)
The Contains method does not use the == operator. What is the problem?
That is correct.
This method [Contains] determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable.Equals method for T (the type of values in the list).
http://msdn.microsoft.com/en-us/library/bhkz42b3(v=vs.110).aspx
You need to override Equals() as well. Note when you overload Equals(), it is almost always correct to also override GetHashCode().
Override Equals and GetHashCode like:
class Element
{
public int id;
protected bool Equals(Element other)
{
return id == other.id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Element) obj);
}
public override int GetHashCode()
{
return id; //or id.GetHashCode();
}
//..... rest of the class
See: List<T>.Contains Method
This method determines equality by using the default equality
comparer, as defined by the object's implementation of the
IEquatable<T>.Equals method for T (the type of values in the list).
So I have an interface, lets call it IInterface.
public interface IInterface : IEquatable<IInterface>
{
string Name { get; set; }
int Number { get; }
Task<bool> Update();
}
Then I try and implement the interface in Implementation.
public bool Equals(IInterface other)
{
if (other == null) return false;
return (this.Name.Equals(other.Name) && this.Number.Equals(other.Number));
}
public override int GetHashCode()
{
return this.Number.GetHashCode();
}
public override bool Equals(object obj)
{
var other = obj as IInterface ;
return other != null && Equals(other);
}
public static bool operator ==(Implementation left, IInterface right)
{
if (ReferenceEquals(left, right)) return true;
if (ReferenceEquals(left, null)) return false;
return left.Equals(right);
}
public static bool operator !=(Implementation left, IInterface right)
{
return !(left == right);
}
The problem I am running into is in a setter:
public IInterface MyIntf
{
get { return _myIntf; }
set
{
if (_myIntf == value) { return; }
_myIntf = value;
}
Intellisense is showing that the equality test there is testing the references only and treating both left and right as objects. I assume this is because there is no operator overload for ==(IInterface left, IInterface right). Of course, I cannot actually implement that function because == requires one of the sides to match the type of the implementing class. How does one properly make sure two interfaces can be checked for equality against each other?
Update
Got it, you cannot implement == for an interface. I will use Equals. Thanks everyone.
You should explicitly call Equals:
if (_myIntf != null && _myIntf.Equals(value)) { return; }
Implementing IEquatable<T> does not impact the == operator.
Use Equals instead of ==:
public IInterface MyIntf
{
get { return _myIntf; }
set
{
if (_myIntf.Equals(value)) { return; }
_myIntf = value;
}
}
Why does this program print "not added" while I think it should print "added"?
using System;
using System.Collections.Generic;
class Element
{
public int id;
public Element(int id)
{
this.id = id;
}
public static implicit operator Element(int d)
{
Element ret = new Element(d);
return ret;
}
public static bool operator ==(Element e1, Element e2)
{
return (e1.id == e2.id);
}
public static bool operator !=(Element e1, Element e2)
{
return !(e1.id == e2.id);
}
}
class MainClass
{
public static void Main(string[] args)
{
List<Element> element = new List<Element>();
element.Add(2);
if(element.Contains(2))
Console.WriteLine("added");
else
Console.WriteLine("not added");
}
}
The Contains method does not use the == operator. What is the problem?
The Contains method does not use the == operator
No - it uses Equals, which you haven't overridden... so you're getting the default behaviour of Equals, which is to check for reference identity instead. You should override Equals(object) and GetHashCode to be consistent with each other - and for sanity's sake, consistent with your == overload too.
I'd also recommend implementing IEquatable<Element>, which List<Element> will use in preference to Equals(object), as EqualityComparer<T>.Default picks it up appropriately.
Oh, and your operator overloads should handle null references, too.
I'd also strongly recommend using private fields instead of public ones, and making your type immutable - seal it and make id readonly. Implementing equality for mutable types can lead to odd situations. For example:
Dictionary<Element, string> dictionary = new Dictionary<Element, string>();
Element x = new Element(10);
dictionary[x] = "foo";
x.id = 100;
Console.WriteLine(dictionary[x]); // No such element!
This would happen because the hash code would change (at least under most implementations), so the hash table underlying the dictionary wouldn't be able to find even a reference to the same object that's already in there.
So your class would look something like this:
internal sealed class Element : IEquatable<Element>
{
private readonly int id;
public int Id { get { return id; } }
public Element(int id)
{
this.id = id;
}
public static implicit operator Element(int d)
{
return new Element(d);
}
public static bool operator ==(Element e1, Element e2)
{
if (object.ReferenceEquals(e1, e2))
{
return true;
}
if (object.ReferenceEquals(e1, null) ||
object.ReferenceEquals(e2, null))
{
return false;
}
return e1.id == e2.id;
}
public static bool operator !=(Element e1, Element e2)
{
// Delegate...
return !(e1 == e2);
}
public bool Equals(Element other)
{
return this == other;
}
public override int GetHashCode()
{
return id;
}
public override bool Equals(object obj)
{
// Delegate...
return Equals(obj as Element);
}
}
(I'm not sure about the merit of the implicit conversion, by the way - I typically stay away from those, myself.)
The Contains method does not use the == operator. What is the problem?
That is correct.
This method [Contains] determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable.Equals method for T (the type of values in the list).
http://msdn.microsoft.com/en-us/library/bhkz42b3(v=vs.110).aspx
You need to override Equals() as well. Note when you overload Equals(), it is almost always correct to also override GetHashCode().
Override Equals and GetHashCode like:
class Element
{
public int id;
protected bool Equals(Element other)
{
return id == other.id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Element) obj);
}
public override int GetHashCode()
{
return id; //or id.GetHashCode();
}
//..... rest of the class
See: List<T>.Contains Method
This method determines equality by using the default equality
comparer, as defined by the object's implementation of the
IEquatable<T>.Equals method for T (the type of values in the list).