Home > Software engineering >  Why does inheriting nested interfaces cause a dependency cycle in C#?
Why does inheriting nested interfaces cause a dependency cycle in C#?

Time:04-20

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.

CodePudding user response:

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.

CodePudding user response:

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).

  • Related