I just wrote a piece of code that behaves quite differently then I would expect. It seems to tell me that I don't know everything yet about objects, so I humbly turn myself to the wisdom of my fellow colleagues of stack overflow to point me in the right direction.
This is the code that baffles me:
[Test]
public void TestMySanity()
{
var oldSet = new Identifier[0];
var newSet = new[]
{
new Identifier("1"),
new Identifier("2")
};
var changes = uut.TrackChanges(oldSet, newSet, x => x).ToArray();
ChangeRecord<Identifier> xx = changes.FirstOrDefault(x => x.New != null && x.Old != null);
if (xx != null)
{
if (xx.Old != null && ((object) xx.Old) == null)
{
Console.WriteLine("How can something be not null and null at the same time?");
}
Assert.Fail("PS: this test expects a change record with Old = null and New = an Identifier");
}
}
Just to be clear -> this code gets into the Console.WriteLine(...)
which should not happen ...
Some things I have tried to get wiser:
Console.WriteLine("Is Object? " + (xx.Old is Object)); // = false
Console.WriteLine("Reference Equals? " + Object.ReferenceEquals(xx.Old, null)); // = true
try { Console.WriteLine(xx.Old.GetType().Name); }
catch (Exception ex)
{ Console.WriteLine("GetType() -> " + ex.Message);}
//Throws: Object reference not set to an instance of an object.
The remarks on this question seem to indicate that the class Identifier is important for this issue, so here it is:
public class Identifier : IComparable, IComparable<Identifier>, IEquatable<Identifier>
{
//Var
protected readonly IComparable Key;
//Constructor
public Identifier(IComparable key)
{
Key = key;
if (key == null) throw new ArgumentNullException("key");
}
//Hashcode (Must be overridden when equals is overridden.)
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override string ToString()
{
return "[" + Key + "]";
}
//Compare
public virtual int CompareTo(object obj)
{
return CompareTo((Identifier)obj);
}
public virtual int CompareTo(Identifier other)
{
if (other == null) return 1;
return Key.CompareTo(other.Key);
}
//Equals
public override bool Equals(object obj)
{
return Equals((obj as Identifier));
}
public virtual bool Equals(Identifier other)
{
if (ReferenceEquals(null, other)) return false;
return Key.Equals(other.Key);
}
//Can be done because a Identifier is immutable
public static bool operator ==(Identifier A, Identifier B)
{
if (ReferenceEquals(A, null)) return false;
return A.Equals(B);
}
public static bool operator !=(Identifier A, Identifier B)
{
return !(A == B);
}
}
Found the error:
public static bool operator ==(Identifier A, Identifier B)
{
if (ReferenceEquals(A, null)) return false;
return A.Equals(B);
}
What happens if B
is null
? A == B
should return true
, sadly your comparison will return false
;
Change it to:
if (ReferenceEquals(A, null)) return ReferenceEquals(B, null);
return A.Equals(B);
In general the point is that by casting to object
like ((object) xx.Old) == null
you are forcing the C# compiler to use the object==
operator, that simply does a object.ReferenceEquals(A, B)
, so you bypass your buggy code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With