0

I made a custom generic list class:

class ComparableList<T> : IList<T>  where T : IComparable<T>

For the generic type, I can use a type that implements IComparable directly like:

class MyComparableClass : IComparable<MyComparableClass>

or I can use a type that inherits from it:

class MyInheritedClass : MyComparableClass

I then decided that IEquatable<T>would be better suited.

So I changed the constraint on the list class and made MyComparableClass implement both IComparable<MyComparableClass> and IEquatable<MyComparableClass>.

For some reason, I can no longer use MyInheritedClass as a generic type for the custom list unless I manually make it implement IEquatable<MyInheritedClass>. I switched the list constraint back to IComparable<T> and all the sudden I can use the inherited class once again.

What's so different between IComparable<T> and IEquatable<T> that makes the rules different?

TheBoxyBear
  • 371
  • 2
  • 15
  • I'm asking why I can use a derived type as a generic that has a constraint if the base type implements the constraint with some interfaces as the constraint but not with others. – TheBoxyBear Feb 23 '21 at 02:04
  • If you implement `IList` you probably should also implement `IReadOnlyList` as well (this is because `IList` does not extend `IReadOnlyList` for historical reasons - this will be fixed _eventually_ but don't expect for at least a few more years). – Dai Feb 23 '21 at 02:20

1 Answers1

4

Your question is kind of rambly and hard to understand, but if we focus on just the last two paragraphs:

For some reason, I can no longer use MyInheritedClass as a generic type for the custom list unless I manually make it implement IEquatable. I switched the list constraint back to IComparable and all the sudden I can use the inherited class once again.

What's so different between IComparable and IEquatable that makes the rules different?

Covariance. IComparable<T> as you wrote it is actually defined as IComparable<in T>, whereas IEquatable<T> is just that.

And that makes sense, because equality is a very strong statement, so the interface for it accepts only the same object type as the one you're testing against (ie no covariance), whereas you can compare apples to oranges if you so wish, one is more orange than the other for example, so having a covariant comparable interface makes sense in this case.

Blindy
  • 65,249
  • 10
  • 91
  • 131