Skip to content

OrderedSet/OrderedCollection/OrderedMap throw ArgumentException for Nullable<T> element types (int?, DateTime?) that Comparer<T>.Default supports #71

Description

@matt-edmondson

What's wrong

When no comparer is passed, the constructors of OrderedSet<T>, OrderedCollection<T> and OrderedMap<TKey,TValue> check whether T is comparable. The check is:

typeof(IComparable<T>).IsAssignableFrom(typeof(T)) || typeof(IComparable).IsAssignableFrom(typeof(T))

Nullable<U> implements neither interface, so the constructor rejects int?, DateTime? and similar types with:

ArgumentException: Type System.Nullable`1[System.Int32] must implement IComparable<T> or IComparable when no comparer is provided.

Comparer<int?>.Default does order these types correctly, with null first, and SortedSet<int?> accepts them.

Locations:

  • OrderedSet.cs: lines 73, 110 and 150
  • OrderedCollection.cs: the equivalent constructors (about lines 83 and 120)
  • OrderedMap: the primary-constructor field initializer, plus the capacity and dictionary constructors

Repro (reproduced)

new OrderedSet<int?>();          // throws ArgumentException
new OrderedCollection<int?>();   // throws
new OrderedMap<int?, string>();  // throws
new SortedSet<int?> { 3, null, 1 }; // fine, Count == 3

Why it matters

Sorting nullable values, such as optional dates or optional priorities, is common. Today it forces users to write a comparer that just wraps Comparer<T?>.Default, and nothing explains why.

Suggested fix

Move the check into one helper that tests the underlying type, and use it in every constructor of all three classes:

Type t = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
bool ok = typeof(IComparable<>).MakeGenericType(t).IsAssignableFrom(t) || typeof(IComparable).IsAssignableFrom(t);

Acceptance: all three containers can be constructed for int?, and they sort nulls the same way Comparer<int?>.Default does. Tests cover this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions