Reputation: 8146
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.
Upvotes: 9
Views: 3268
Reputation: 41
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.
Upvotes: 4
Reputation: 2608
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).
Upvotes: 1
Reputation: 660513
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?
Upvotes: 31
Reputation: 9847
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.
Upvotes: 2
Reputation: 40838
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.
Upvotes: 1