2

The C# compiler rejects the following code:

class A { }
class B { }

interface IInterface<T> { }

abstract class BaseType<T> : IInterface<T>, IInterface<B> where T : A { }

With an error on BaseType<T> saying:

'BaseType<T>' cannot implement both 'IInterface<T>' and 'IInterface<B>' because they may unify for some type parameter substitutions

When omitting the type constraint on T so that it does not have to inherit from A, I can see why this would be a problem, but with this constraint in place everything should be fine right?

Is there a scenario I am missing, or is this just a case of the compiler not figuring out that these types are disjoint?

Dai
  • 141,631
  • 28
  • 261
  • 374
JAD
  • 2,035
  • 4
  • 21
  • 35
  • Consider the case when you change `class B {}` to `class B : A {}`. – Dai Aug 08 '23 at 13:14
  • Well yeah, in that case I understand that `BaseType` ends up implementing `IInterface` twice. My confusion is that since `B` doesn't inherit from `A`, and `T` *needs* to inherit from `A`, there shouldn't be a collision. – JAD Aug 08 '23 at 13:15
  • @Dai one might argue that this would be the case when the code should not compile. Not the one in the question =) – Guru Stron Aug 08 '23 at 13:16

1 Answers1

2

This behavior is described in the 18.6.3 Uniqueness of implemented interfaces section of the language specification:

To determine if the interface list of a generic type declaration is valid, the following steps are performed:

  • Let L be the list of interfaces directly specified in a generic class, struct, or interface declaration C.
  • Add to L any base interfaces of the interfaces already in L.
  • Remove any duplicates from L.
  • If any possible constructed type created from C would, after type arguments are substituted into L, cause two interfaces in L to be identical, then the declaration of C is invalid. Constraint declarations are not considered when determining all possible constructed types.

So it is not about compiler not being able figuring something out but it following the spec.

Note that there are others places when the generic constraints are ignored - for example in method signatures (i.e. generic constraints are not part of the method signature).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Okay, so it's just a case of "status-by-design", I guess to avoid potential complexity. – JAD Aug 08 '23 at 13:18
  • @JAD yes, I would agree that potential complexity is the source of the issue. While it not that problematic with classes (due to the lack of multiple inheritance), but when you will switch from classes to interfaces... – Guru Stron Aug 08 '23 at 13:20
  • 1
    Or nested generics – JAD Aug 08 '23 at 13:22
  • Based on the rest of the section of the spec you linked, it looks like the following would work: `abstract class BaseOne : IInterface { }` and then `abstract class BaseType : BaseOne, IInterface where T : A { }` – JAD Aug 08 '23 at 13:39
  • @JAD yes it will work, because in case of type-hierarchy implementing the same interface is just fine (C# does not restrict that, for quite obvious reasons). Also in case of "collisions" (I bet there is something in spec about it) compiler can select some concrete method to call - see [@sharplab.io](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQBJAPAewEYBWApgMbAB8ABAB6UC8lURA7pQBpYDyhpwANPmJlyACgCUAbnTUAdADFxUtOgCWUYETAAzAIYkilbABVy6AN7pKVynAAslBZPQBfdInYAhHQGciWAKpUIIYB5GZ2IYHy4vRUcEgAnCIARBhQTGDJkq5o7hz+lHwAakGePn6BfCEllAD0tZQAomBgOGDB2IGUOlAAJtVUJDhQWgA2KmTm6JbWEdgl0WKxNokpnBAamU5ozkA===). – Guru Stron Aug 08 '23 at 13:55