9

This has caught me out once or twice in C#. I can write code such as this

class Node
{
    class Connection
    {
        public Connection(Node node, string label)
        {
            this.Node = node;
            this.Label = label;
        }
        public Node Node { get; private set;  }
        public string Label { get; private set; }            
    };

    IEnumerable<Connection> IncomingConnections() // ...
    IEnumerable<Connection> OutgoingConnections() // ...
}

but if I write

interface INode
{
    class Connection
    {
        public Connection(INode node, string label)
        {
            this.Node = node;
            this.Label = label;
        }
        public INode Node { get; private set; }
        public string Label { get; private set; }
    };

    IEnumerable<Connection> IncomingConnections();
    IEnumerable<Connection> OutgoingConnections();
}

I get the compile error

error CS0524: 'Connection': interfaces cannot declare types

I understand the restriction, but what I'm interested in is why. I can certainly have nested types in a C++ "interface" (which is just a class with abstract members so no surprise), and apparently it's possible in Java too, see Interfaces Cannot Declare Type Issue C#. So given that C# learnt some things from Java, why is it lacking in this respect (if indeed it is lacking)?

(Apologies if this has already been addressed elsewhere. I also found Interfaces cannot declare types and Why can't I put a delegate in an interface? but they didn't seem to address my question directly.)

Edit

I thought I'd just add a note to say that in the Java world it seems at first sight to be an open question as to whether it's ok to nest a class within an interface. See https://stackoverflow.com/a/9098321/834521. I don't think I'm being silly in asking why the same can't apply to C#.

Edit

Brief summary / quotes from Framework Design Guidelines, 2ed, section 4.9 pp115-117.

  • Do use nested types when e.g. the nested type needs access to private members of the enclosing type.
  • Don't use public nested type for grouping; use namespaces for this.
  • Avoid publicly nested types unless you really know what you're doing. (Main motivation: explicitly creating nested types is confusing for less skilled developers. However implicitly creating, e.g. via collection enumerators, is ok.)
  • Don't use nested types if the nested type is it is going to be used or instantiated outside of the containing type (both of these argue for independence of the nested type from the containing type).
  • Don't use as a member of an interface.
18
  • 16
    Because an interface is... well, it's an interface, not an implementation. If you want an implementation, make it a class, not an interface. You can declare a member or property of type Connection on the interface, but the implementation of that type is going to be described elsewhere. Imagine if you had to refer to that particular interface every time you needed to create an object of type Connection. That would be pretty awkward, wouldn't it? Commented Apr 22, 2013 at 16:07
  • 3
    C++ has no interfaces...
    – Emond
    Commented Apr 22, 2013 at 16:14
  • 2
    @TooTone You're missing the whole point of an interface. You need to look at the purpose of an interface in C# and understand what they're for. Once you understand what they are in C#, you'll see that nesting a class in an interface makes no sense. It's not like the designers even had to ask "should we allow this?" You sound like a nut: "Language X has a feature that people made up the word Y for. Why doesn't the feature named Y work in language Z like it does in language X?"
    – itsme86
    Commented Apr 22, 2013 at 16:19
  • 2
    @TooTone That's simply not a bad thing. You can use namespaces to prevent intellisense from being cluttered up and to convey that they types "belong" together.
    – Servy
    Commented Apr 22, 2013 at 17:01
  • 2
    @TooTone: In general, such organization is done with namespaces. Commented Apr 22, 2013 at 17:42

5 Answers 5

31

Why can't an interface contain types?

Before digging into the question let me clear up a couple of things.

First, the CLR type system does permit nested types inside interfaces. It would be entirely possible to create a version of C# or VB or whatever tomorrow that supported interfaces, delegates, classes, structs and enums to be declared inside interfaces, and it would run on the existing CLR.

Second, I will give you my usual pushback on questions of the form "why does the C# language not implement feature X?" The answer is the same for all values of X. In order to be implemented a feature must be: thought of, designed, specified, implemented, tested and shipped to customers. If any one of those six things does not happen then there is no feature. Feature X is not implemented because one or more of those things did not happen.

Third, the C# compiler team (which I am no longer on) does not have to provide any explanation for not implementing a feature. Features cost money, the budget is finite, and therefore the onus is on the person requesting the feature to justify its benefits against its costs.

Fourth, "why" questions are hard to answer and "why not" questions are even harder.

So, with that said, I'll reject your question and replace it with a question I can answer:

Suppose this feature request had been proposed to the C# design team. What arguments would you have made against it?

  • The feature, though legal in the CLR, is not legal in the CLS. There are lots of features in C# that are not legal in the CLS, but since the CLS guidance is specifically do not nest types in interfaces because most languages do not support it, implementing the feature in C# is essentially encouraging people to write libraries that cannot be used in other languages. The proposed feature encourages a bad programming practice.

  • Nested types give you three main advantages. First, they have access to the private members of their enclosing types. This is not a benefit for interfaces, which have no private members. Second, they provide a convenient way to contain a specific private implementation detail of the outer type. This is not a benefit for interfaces, which presumably could not have a private nested type, and which do not have implementation details by definition. Third, they provide a convenient way to associate one type with another; however, this is better done by namespaces.

  • No one else to my knowledge is requesting the feature. Let's not spend money on a feature that hardly anyone wants when there are plenty of features that customers do want.

  • Implementing the feature doesn't make the language more powerful or more expressive in any way in of itself.

  • Implementing the feature is not a stepping-stone to some more awesome feature that I am aware of. The feature doesn't tie into any other "theme". It's a "completionist" feature that eliminates a small non-orthogonality, not a useful feature.

  • There exists an easy workaround for the lack of the feature; just make the nested type a top-level type.

That's the case against. Without someone to advance a case for the feature, it's not going to last in the design committee meeting for more than maybe five minutes tops. Do you care to advance a case for the feature?

4
  • Many thanks for your answer which covers technical and business reasons comprehensively. I've learnt a lot. I now have the Framework Design Guidelines and have read the relevant section, and will start to work through the whole book.
    – TooTone
    Commented Apr 23, 2013 at 9:03
  • 1
    @TooTone: You're welcome! If you are interested in other topics in C# language design, I write a blog about that. Commented Apr 23, 2013 at 13:36
  • I've a user case: Because the restriction of co-variance / contra-variance for delegates and interfaces only, this check is preventing from writing something like Func<A1,A2>.To<R> as a more explicit version than Func<A1, A2, R>
    – Olmo
    Commented Apr 10, 2014 at 15:12
  • 1
    IMO interfaces and enums should be allowed in interfaces as both are implementation agnostic. This would allow much better code design.
    – CodeAngry
    Commented Jun 16, 2015 at 17:57
4

Would like to add, that from C# 8.0, interface is permitted to use nested types.

Default interface methods - C# 8.0 specification proposals | Microsoft Docs (emphasis added)

The syntax for an interface is extended to permit:

  • member declarations that declare constants, operators, static constructors, and nested types;

So something like below is now legal.

interface ISpectrum {
    [Flags]
    enum Palette { Red = 1, Green = 2, Blue = 4 }
    Palette Color { get; }
}

Whether this is good practice has been discussed in other answers, but I personally find interface-specific enum has its uses.

Interestingly, despite this change being listed as being part of the default interface implementation, most of which requires new runtime i.e. .NET Core 3.0/.NET Standard 2.1 and later, interface with nested type but without any implementation does compile and can be used in .NET Framework 4.8, as long as Roslyn CSC supporting compilation in C# 8.0 is used.

I assume this is due to the fact that CLR has been supporting nested type in interface for all this time, as Eric Lippert stated in the answer here.

2

There are only a few reasons why it makes sense to nest types. The main reason is to define them as private so that only the container class has access to them. The container class would use these private types in its own implementations.

Since an interface isn't an implementation, there is no good reason to nest types inside of it. It would be useless. It would be like a farmer trying to use a kitten to help him plow his fields. Theoretically, that might be possible at least to try, but it wouldn't serve any practical purpose.

Looking at the code provided, I would suggest promotion of the Connection class to a top-level type. If you want to organize your types according to function, that is what namespaces are for. Create a folder structure in your project of how the types are organized, and change the namespaces to reflect that.

7
  • Thanks. You seem to go further than some others who commented, saying I shouldn't even nest types publicly within a class. I suppose where I'm coming from is to try to make associations in my code somehow between classes that are strongly related, to try to make the design clearer. I could, e.g., have used Tuple<INode, string> instead of Connection in my OP. However although when I started programming C# a few months ago I used Tuples a lot for this sort of class, I stopped after I found the Item1 / Item2 / Item3 not v readable. So now I've ended up with small classes "on their own".
    – TooTone
    Commented Apr 22, 2013 at 16:45
  • 1
    In the case of 'types' meaning classes this is pretty much it, but in the case of 'types' meaning delegates used within the interface I'm not sure I agree. The purpose (were it possible) would be to fully define the interface within the interface deceleration rather than reference a trivial external type.
    – Yaur
    Commented Apr 22, 2013 at 17:07
  • @Yaur That's a matter of opinion. My opinion is that if the type is public, it's annoying to have to reference it through a containing type, even if it's just a delegate. If the type is public, I think it deserves to be a top-level type. Once again, that's just my opinion though.
    – Dan
    Commented Apr 22, 2013 at 17:36
  • 1
    Dan, your opinion is echoed by the Framework Design Guidelines, which explicitly discourage public nested types. Commented Apr 22, 2013 at 19:21
  • Eric: What about, for example, the mutable enumerator structs exposed by many of the BCL collection types? Is this considered a reasonable exception to the guidelines (because those types will rarely be accessed by name), or is it just a case of the BCL violating the guidelines? Commented Apr 22, 2013 at 20:34
1

From the C# specification, section 13.2:

An interface cannot contain constants, fields, operators, instance constructors, destructors, or types, nor can an interface contain static members of any kind.

Nested types are a kind of static member, so it is more consistent to not allow nested types, than to make them a special case.

1

While it's been mentioned in a comment on this post, I wanted to reiterate here that you can use VB.Net to create interfaces with nested types, and then use that interface and all of its nested types unrestricted in C#. Also, you can export that code to IL using a tool like ILDasm or ILSpy and then weave it back into your library using the Visual Studio ILSupport extension (provided you don't actually need it directly in your library).

As for reasons why someone might want to do this, here are two. The first is to provide the blueprint/pattern definitions for a set of coupled interfaces with shared generic type parameters required to support a common concept, such as entity management. For example:

public  interface   IEntity
                    <
                        TIEntity, 
                        TDataObject, 
                        TDataObjectList, 
                        TIBusiness, 
                        TIDataAccess, 
                        TPrimaryKeyDataType
                    >
        where       TIEntity            : IEntity<TIEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKeyDataType>
        where       TDataObject         : IEntity<TIEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKeyDataType>.BaseDataObject
        where       TDataObjectList     : IEntity<TIEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKeyDataType>.IDataObjectList
        where       TIBusiness          : IEntity<TIEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKeyDataType>.IBaseBusiness
        where       TIDataAccess        : IEntity<TIEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKeyDataType>.IBaseDataAccess
        where       TPrimaryKeyDataType : IComparable<TPrimaryKeyDataType>, IEquatable<TPrimaryKeyDataType>
{

    public  class       BaseDataObject
    {

        public  TPrimaryKeyDataType Id  { get; set; }

    }

    public  interface   IDataObjectList : IList<TDataObject>
    {

        TDataObjectList ShallowClone();

    }

    public  interface   IBaseBusiness
    {
        void            Delete(TPrimaryKeyDataType id);
        TDataObjectList Load(TPrimaryKeyDataType id);
        TDataObjectList Save(TDataObjectList items);
        bool            Validate(TDataObject item);
    }

    public  interface   IBaseDataAccess
    {
        void            Delete(TPrimaryKeyDataType id);
        TDataObjectList Load(TPrimaryKeyDataType id);
        TDataObjectList Save(TDataObjectList items);
    }

}

The above could alternatively be solved by repeating the generic type parameters and constraints on each nested interface/class outside of the container interface, but that quickly gets messy. Another great alternative would be if .Net supported generic type parameters on namespaces, but sadly that is not currently supported.

The second reason to nest types in an interface would be to provide a composition/mixin interface for defining composite interfaces and a nested supporting composer class providing functionality to accept mixin instances to construct a new dynamic class that implements the composite interface and forwards the calls to the mixins. Such as the following:

public  interface   IComposite<TIComposite, TIMixin1, TIMixin2>
        where       TIComposite : class,    IComposite<TIComposite, TIMixin1, TIMixin2>, TIMixin1, TIMixin2
        where       TIMixin1    : class
        where       TIMixin2    : class
{

    public  class   Composer
    {

        public  static  TIComposite     Create(TIMixin1 mixin1, TIMixin2 mixin2)
        {
            ...
        }

    }

}

You could then create interfaces that extend the above IComposite interface which would already include an appropriate composer for the extended interface. Note that you would also likely have other variations of the IComposite interface to support higher counts of mixins (similar to Func and Action, since .Net does not currently support variadic generic type parameters).

You could use it like in the following example:

public  interface   IPerson
{
    string  FirstName;
    string  LastName;
}

public  interface   ILock
{
    object  GetLock();
}

public  interface   ILockablePerson : IComposite<ILockablePerson, IPerson, ILockable>, IPerson, ILockable {}

public  class       Person : IPerson
{
    public  string  FirstName   { get; set; }
    public  string  LastName    { get; set; }
}

public  class       Lock : ILock
{
    private
    readonly    object  lock        = new object();
    public      object  GetLock()   { return this.lock; }
}

public  class       UseLockablePerson
{

    public  void    Main()
    {
        var     lockablePerson  = ILockablePerson.Composer.Create(new Person(), new Lock());

        lock(lockablePerson.GetLock())
        {
            lockablePerson.FirstName    = "Bob";
        }
    }

}

While it is possible to create the composer class outside of the IComposite interface, discovery of its composition support or intent would not be as easy as seeing the Composer appear in IntelliSense. In this case the nested Composer class as actually being used to provide a static method (Create) for the interface (which is another unsupported option).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.