Skip to content

IComparable<T>.CompareTo<T> inconsistencies #99722

@rwv37

Description

@rwv37

Description

The documentation for IComparable<T> says, in part:

If you implement IComparable<T>, you should overload the op_GreaterThan, op_GreaterThanOrEqual, op_LessThan, and op_LessThanOrEqual operators to return values that are consistent withCompareTo(T)

And the documentation for CompareTo(T) in turn says, in part:

By definition, any object compares greater than null

The example given in the IComparable documentation is consistent with these two things:

public int CompareTo(Temperature other)
{
    // If other is not a valid object reference, this instance is greater.
    if (other == null) return 1;

    // The temperature comparison depends on the comparison of
    // the underlying Double values.
    return m_value.CompareTo(other.m_value);
}

// Define the is greater than operator.
public static bool operator >  (Temperature operand1, Temperature operand2)
{
    return operand1.CompareTo(operand2) > 0;
}

However, these seem to contradict the documentation for compiler warning CS0464, which says, in part:

A comparison like i == null can be either true of false. A comparison like i > null is always false.

Side note: There's a little typo in that documentation, which maybe you'd want to fix - "true of false" rather than "true or false". Anyway:

I tried it out with a very common class (Int32) just to see what it does. At least for that class, for the > and >= operators:

  1. The ```CompareTo```` "any object compares greater than null" is correct, and
  2. The CS0464 "A comparison like i > null is always false", is correct, and
  3. Put those together and the IComparer<T> "you should overload the op_GreaterThan, op_GreaterThanOrEqual, op_LessThan, and op_LessThanOrEqual operators to return values that are consistent withCompareTo(T)" is necessarily broken.

I guess the word "should" in there doesn't imply "must", but... the whole IComparable/IEquatable/op_Whatever/GetHashCode/Equals<T>/Equals<Object>/Object.Equals/whatever-else-I-am-missing stuff is notoriously difficult, with lots of people confused over exactly what should be implemented and how, and this sure doesn't help.

Reproduction Steps

Note: > is demonstrated here, but >= behaves the same way:

Console.WriteLine($"nonnull > null: {new Int32() > null}");
Console.WriteLine($"nonnull.CompareTo(null) > 0: {(new Int32()).CompareTo(null) > 0}");

Expected behavior

LOL, I don't know. That's a big part of the problem. According to IComparable<T> and CompareTo<T> (i.e. "consistent operator/CompareTo behavior" plus "objects CompareTo greater than null"), it seems it should output:

nonnull > null: True
nonnull.CompareTo(null) > 0: True

Whereas according to CompareTo<T> and CS0464 (i.e., "objects CompareTo greater than null" plus "operators other than == and != are always false for null") it seems like it should output:

nonnull > null: False
nonnull.CompareTo(null) > 0: True

And according to IComparable<T> and CS0464 (i.e. ""consistent operator/CompareTo behavior" plus "operators other than == and != are always false for null"), it seems like it should be:

nonnull > null: False
nonnull.CompareTo(null) > 0: False

Actual behavior

At least in the case of Int32:

nonnull > null: False
nonnull.CompareTo(null) > 0: True

Regression?

No response

Known Workarounds

No response

Configuration

  • version of .NET: 8.0
  • OS and version: Windows 11
  • architecture: x64
  • specific to that configuration: I don't know. I doubt it.
  • If you're using Blazor: I'm not.

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions