3

This example compiles:

public interface IOuter : IInner {

}

public interface IInner {

}

And so does this:

public class Outer : Outer.IInner {

    public interface IInner { }
    
}

But when Outer is an interface, we get a compilation error:

public interface IOuter : IOuter.IInner {

    public interface IInner { }
    
}
IOuter.cs(3, 18): [CS0529] Inherited interface 'IOuter.IInner' causes a cycle in the interface hierarchy of 'IOuter'

Why does inheriting a nested interface cause a cycle?

Does IInner somehow implicitly inherit IOuter, preventing IOuter from implementing IInner? It doesn't seem like it, because this compiles:

public interface IOuter {

    string OuterProperty { get; }
    
    public interface IInner {

        string InnerProperty { get; }

    }
    
}

public class InnerImplementation : IOuter.IInner {

    // We only need to implement IInner's members, not IOuter's,
    // so IOuter does not seem to be part of the dependency hierarchy here
    public string InnerProperty { get; }

}

I couldn't see anything at all about nested interfaces in the Interfaces section of the C# language spec. Are nested interfaces in general just undefined behaviour? If so, is there a reason for that?


Edit
Looking at the language spec for type declarations, here, it says:

A type_declaration can occur as a top-level declaration in a compilation unit or as a member declaration within a namespace, class, or struct.

Interfaces are not specified here, so perhaps this is indeed undefined behaviour. Still, if anyone knows if nested interface types were ever considered and rejected, I'd like to know why.

  • See below answers. I was looking at the wrong section of the spec.
brads3290
  • 1,926
  • 1
  • 14
  • 20
  • The issue here is that IOuter is inheriting from itself. – anastaciu Apr 19 '22 at 19:44
  • @anastaciu Thanks for your comment. Could you please explain how this is happening? I can see that is what the error message says, but I don't understand _why_ `IOuter` is inheriting from itself. – brads3290 Apr 19 '22 at 19:45
  • Just FYI if you'll make both classes - it will result in similar "error CS0146: Circular base type dependency involving 'Outer.IInner' and 'Outer'" compilation error. – Guru Stron Apr 19 '22 at 19:50
  • IOuter is an interface, you cannot make IOuter inherit from IOuter, regardless of the fact that you want to Inherit a member o IOuter, there is not much more to explain I think, the language doesn't allow it, as you explained, you can do this if it's not an interface, maybe someone can explain why this is so, I don't really know. – anastaciu Apr 19 '22 at 19:51
  • @GuruStron - Interesting, good catch! Any idea why? It seems a little bit more intuitive with classes - `Inner` does form part of the definition of `Outer` (which, conceptually, isn't really so with interfaces - if you implement `IOuter`, you don't have to care about `IInner` at all) so I can imagine there being a problem there. But I still can't guess the exact problem / reason why it is disallowed – brads3290 Apr 19 '22 at 19:55
  • 1
    @anastaciu Yes I understand I cannot make `IOuter` inherit from `IOuter` - I would like to know why this particular arrangement of code leads to `IOuter` inheriting from `IOuter`. `IInner` is defined _within_ `IOuter`, but `IInner` is a separate interface. As demonstrated, I can implement `IInner` on a class without implementing any members of `IOuter`, so clearly `IOuter` is not part of the inheritance hierarchy of `IInner`. – brads3290 Apr 19 '22 at 19:59
  • You still need to use IOuter to acces IInner, some indirect dependency may exist, but, as I said, I can't tell you why, otherwise I woud have answered the question instead of commenting ;) – anastaciu Apr 19 '22 at 20:05
  • 1
    @anastaciu "some indirect dependency may exist" - yeah, that's pretty much the crux of my question. Thanks for contributing anyway :-) – brads3290 Apr 19 '22 at 20:06

2 Answers2

4

The same behaviour exists for classes:

// doesn't compile either (with a different error)
public class Outer : Outer.IInner {

    public class Inner { }
    
}

And this is specified in the spec here:

It is a compile-time error for a class to depend on itself. For the purpose of this rule, a class directly depends on its direct base class (if any) and directly depends on the nearest enclosing class within which it is nested (if any). Given this definition, the complete set of classes upon which a class depends is the transitive closure of the directly depends on relationship.

See also: Why can't a class extend its own nested class in C#?

The restriction of not being able to do this with interfaces is an extension of this rule that also takes interfaces into account. The reason why you don't see anything about this in the spec, is because the spec is only for C# 6, and the ability to write nested interfaces was only added in C# 8. You'd have to look at Default Interface Methods under the "C# 8 features" section to find the new changes to the "Interfaces" section to the spec. It is stated in the binding base clauses section:

Interfaces now contain types. These types may be used in the base clause as base interfaces. When binding a base clause, we may need to know the set of base interfaces to bind those types (e.g. to lookup in them and to resolve protected access). The meaning of an interface's base clause is thus circularly defined. To break the cycle, we add a new language rules corresponding to a similar rule already in place for classes.

While determining the meaning of the interface_base of an interface, the base interfaces are temporarily assumed to be empty. Intuitively this ensures that the meaning of a base clause cannot recursively depend on itself.

We used to have the following rules:

<the rules quoted above>

[...]

We are adjusting them as follows:

When a class B derives from a class A, it is a compile-time error for A to depend on B. A class directly depends on its direct base class (if any) and directly depends on the type within which it is immediately nested (if any).

When an interface IB extends an interface IA, it is a compile-time error for IA to depend on IB. An interface directly depends on its direct base interfaces (if any) and directly depends on the type within which it is immediately nested (if any).

Given these definitions, the complete set of types upon which a type depends is the reflexive and transitive closure of the directly depends on relationship.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Hmm, follow up question from the quote "An interface directly depends on its direct base interfaces (if any) **and directly depends on the type within which it is immediately nested (if any).**" Do you know why then, it is OK to do `Outer : Outer.IInner` (as in the second code block in my original question)? – brads3290 Apr 19 '22 at 20:29
  • I suppose implementing an interface doesn't create a dependency on that interface, but an interface inheriting an interface or a class inheriting a class, does? – brads3290 Apr 19 '22 at 20:31
  • @brads3290 exactly! There are only two cases for the "directly depends on" relationship: "When a class B derives from a class A..." and "When an interface IB extends an interface IA...". And the "depends on" relationship is the reflexive and transitive closure of that, so `Outer` and `Outer.IInner` are not related by "depends on" in this case. – Sweeper Apr 19 '22 at 20:34
2

According to the specification:

It is a compile-time error for a class to depend on itself. For the purpose of this rule, a class directly depends on its direct base class (if any) and directly depends on the nearest enclosing class within which it is nested (if any)

With the one of the examples being :

class A : B.C {}
class B : A
{
    public class C {}
}

results in a compile-time error because A depends on B.C (its direct base class), which depends on B (its immediately enclosing class), which circularly depends on A.

i.e. B.C depends on B

Prior to C# 8 interfaces could not declare types (fiddle) which was changed with introduction of default interface methods and it seems that the same rules apply here, check binding base clauses specification section:

When an interface IB extends an interface IA, it is a compile-time error for IA to depend on IB. An interface directly depends on its direct base interfaces (if any) and directly depends on the type within which it is immediately nested (if any).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132