8

What should IEquatable<T>.Equals(T obj) do when this == null and obj == null?

1) This code is generated by F# compiler when implementing IEquatable<T>. You can see that it returns true when both objects are null:

    public sealed override bool Equals(T obj)
    {
        if (this == null)
        {
            return obj == null;
        }
        if (obj == null)
        {
            return false;
        }

        // Code when both this and obj are not null.
    }

2) Similar code can be found in the question "in IEquatable implementation is reference check necessary" or in the question "Is there a complete IEquatable implementation reference?". This code returns false when both objects are null.

    public sealed override bool Equals(T obj)
    {
        if (obj == null)
        {
            return false;
        }

        // Code when obj is not null.
    }

3) The last option is to say that the behaviour of the method is not defined when this == null.

Community
  • 1
  • 1
Radek Micek
  • 447
  • 2
  • 9
  • 5
    The case `this == null` should *never* (!!!) occur. Not sure about the peculiarities of F# but in C# this holds. – Konrad Rudolph Nov 11 '11 at 13:59
  • 1
    There is no check `this != null` when the method is invoked by `call` opcode. – Radek Micek Nov 11 '11 at 15:42
  • @RadekMicek, that's why the C# compiler emits `callvirt` for non-static static method calls. But perhaps F# only emits `call`... – Thomas Levesque Nov 11 '11 at 15:47
  • @Radek Even so, the above wouldn’t ever be the right semantics in C#, and the `null` check in this situation is never needed. Even in VB, which has a `MyClass` keyword which emits a `call` instead of `callvirt`, this situation cannot arise. – Konrad Rudolph Nov 11 '11 at 16:08
  • 4
    @KonradRudolph: But still possible in the CLR... – leppie Nov 11 '11 at 16:49
  • I started reading this an decided I preferred the care bear world of the option type. This discussion never happened. – David Grenier Nov 30 '11 at 23:24

7 Answers7

9

leppie is right. Just to elaborate on his answer (and confirm his suspicion that F# doesn't guarantee this != null): discriminated unions may be marked with the attribute [<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>] allowing cases to be represented by the value null. Option<'T> is such a type. The None case is represented by null at run-time. (None : option<int>).Equals(None) is syntactically valid. Here's a fun example:

[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type Maybe<'T> =
  | Just of 'T
  | Nothing
  [<CompilationRepresentation(CompilationRepresentationFlags.Instance)>]
  member this.ThisIsNull() = match this with Nothing -> true | _ -> false

Decompiling ThisIsNull with Reflector shows

public bool ThisIsNull()
{
    return (this == null);
}

And the result:

Nothing.ThisIsNull() //true
Daniel
  • 47,404
  • 11
  • 101
  • 179
3

The reason F# does this (I suspect) to optimize empty lists as null.

By adding this check, it allows one to call an instance method on a null instance without any problems.

See my blog post from a while back.

In C#, this is irrelevant.

To answer the question:

It should return true as both instances are null and deemed equal.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • But empty list in F# is defined as non-null `internal static readonly FSharpList _unique_Empty = new FSharpList();` – Radek Micek Nov 11 '11 at 17:14
  • 3
    As others have noted, `null` isn't used as the representation for empty lists in F#. However, it is used as the representation of the option value `None`. – kvb Nov 11 '11 at 17:21
1

If this is null, the code can't be called, so that case needn't be considered (in C# anyway, there are cases where languages allow a null object to have a method dereferenced though obviously if it internally examines any of its non-existent fields it will error. Consider:

return x.Equals(y);

If x is null, we don't even get to call into Equals for the null check to count.

Hence we need only consider:

public bool Equals(T obj)
{
  if(obj == null)
    return false;
  //logic defining equality here.
}

Where the possibility of both objects being null does come up, is when we are examining them from a static == operator override or from an IEqualityComparer<T> implementation:

public bool Equals(T x, T y)
{
  if(x == null)
    return y == null;
  if(y == null)
    return false;
  //logic defining equality here.
}

Note that a useful shortcut here if equality can be lengthy to determine (e.g. comparing long strings), then we may take advantage of the fact that identity entails equality - that is something is always equal to itself, even Ayn Rand could figure that out ;) There are also algorithms that make comparing an item with itself quite common, making this shortcut well worth including. In this case the identity comparison already includes the check for both being null, so we leave it out again:

public bool Equals(T x, T y)
{
  if(ReferenceEquals(x, y))
    return true;
  if(x == null || y == null)
    return false;
  //logic defining equality here.
}
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

For most methods I assume undefined behavior when called with this==null. That's because most programmers write their code under the assumption that this!=null, which is guaranteed by the C# specification if the calling code is written in C#.

That's why every sane caller of x.Equals(y) should either know for sure that that x is not null, or add a manual null check.

In most cases I wouldn't call Equals directly at all, but instead use EqualityComparer<T>.Default.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
0

I would definitelly go with option 1:

    if (this == null)
    {
        return obj == null;
    }
    if (obj == null)
    {
        return false;
    }

null object always equals null object.

zmilojko
  • 2,125
  • 17
  • 27
  • 1
    But this is never equal to null. – Jon Hanna Nov 11 '11 at 15:40
  • @JonHanna, in C# this is true, but the code shown by the OP is generated by F# (and presumably decompiled to C# by Reflector or a similar tool) – Thomas Levesque Nov 11 '11 at 15:49
  • @ThomasLevesque Yeah, that's why they're asking the question, but it makes no sense to leave it in the engineered C#. In C# (as labelled) the test can't be true. – Jon Hanna Nov 11 '11 at 16:06
  • 1
    @JonHanna, sure, but what if the method is written in C# and gets called by code in another language that doesn't check for null before it calls the method? (e.g. if it emits `call` instead of `callvirt`) – Thomas Levesque Nov 11 '11 at 16:08
  • @ThomasLevesque then won't it throw as soon as it gets as far as examining the members to check for equality in more detail? Just like any case of using `call` on a method that doesn't guarantee to behave well in the face of the object being null can do that, no matter what the language. – Jon Hanna Nov 11 '11 at 16:21
  • @Jon it will already `return false` at the `other==null` check which is necessary on reference types. Personally I'd probably skip the check, since I consider `null.Equals(other)` undefined behavior. – CodesInChaos Nov 11 '11 at 16:27
  • @CodeInChaos Right you are, it'll return false in that case, but it'll throw if other != null. Even more right you are on undefined behaviour, otherwise we'd have to add null-checks to every single method and property. It just neither makes sense to do this in C# nor to assume all methods will behave correctly with `call` in IL (there's some nice things one can do with languages that let you call on a null object, but generally you want to check its allowed in the case of the given method first, rather than assume). – Jon Hanna Nov 11 '11 at 16:36
0

Sample code is in the MSDN: http://msdn.microsoft.com/en-us/library/ms131190.aspx?ppud=4

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
  • 2
    IMO that code sample is pretty dubious. Mutable identity, and not comparing all public properties are both patterns I'd avoid. – CodesInChaos Nov 11 '11 at 16:33
0

If this==null you will get a runtime exception calling Equals() on that object.

Jobo
  • 1,084
  • 7
  • 14
  • `this` can never be null; in order to have access to `this`, you must be within an instance of the type – Mark Avenius Nov 11 '11 at 14:18
  • i sure know that, but this == null never makes sense, because you get a runtime exception before getting that far. – Jobo Nov 11 '11 at 14:38
  • @Jobo - The only possible exception would be a StackOverflowException. Is that what you are referring to? – CodeNaked Nov 11 '11 at 15:51
  • 2
    `this!=null` is only guaranteed if you call the method directly C#(and probably most other .net languages). It is possible to call instance methods with `this==null` from other languages, or by using a delegate. – CodesInChaos Nov 11 '11 at 16:13
  • `this` can be `null`, just not in C#. – leppie Nov 11 '11 at 16:57