Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can (x.y != null && ((object)x.y) == null) ever be true?

Tags:

c#

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);
    }
}
like image 276
Frederick Grumieaux Avatar asked Jan 06 '23 09:01

Frederick Grumieaux


1 Answers

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.

like image 59
xanatos Avatar answered Jan 18 '23 12:01

xanatos