为什么接口不能包含类型?

时间:2013-04-22 16:06:23

标签: c#

这让我在C#中遇到过一两次。我可以编写这样的代码

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() // ...
}

但如果我写

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();
}

我收到编译错误

  

错误CS0524:'连接':接口无法声明类型

我理解限制,但我感兴趣的是 为什么 。我当然可以在C ++“接口”中使用嵌套类型(这只是一个具有抽象成员的类,所以毫不奇怪),显然它也可以在Java中看到Interfaces Cannot Declare Type Issue C#。因此,考虑到C#从Java中学到了一些东西,为什么它在这方面缺乏(如果它确实缺乏)?

(道歉,如果已经在其他地方解决了这个问题。我还发现Interfaces cannot declare typesWhy can't I put a delegate in an interface?,但他们似乎没有直接解决我的问题。)

修改

我想我只是添加一个注释,说在Java世界中,乍一看似乎是一个悬而未决的问题,是否可以在接口中嵌套一个类。见https://stackoverflow.com/a/9098321/834521。我不认为我为什么同样不能适用于C#而感到愚蠢。

修改

摘要/引自Framework Design Guidelines,2ed,第4.9节第115-117页。

  • 使用嵌套类型,例如嵌套类型需要访问封闭类型的私有成员。
  • 不要使用公共嵌套类型进行分组;为此使用名称空间。
  • 避免使用公开嵌套的类型,除非您真的知道自己在做什么。 (主要动机:明确创建嵌套类型对于技能较低的开发人员来说很困惑。但是,例如通过集合枚举器隐式创建是可以的。)
  • 如果嵌套类型是在包含类型之外使用或实例化的,则不要使用嵌套类型(这两种类型都支持嵌套类型与包含类型的独立性)
  • 不要用作界面的成员。

5 个答案:

答案 0 :(得分:30)

  

为什么接口不能包含类型?

在深入研究这个问题之前,请先澄清一些事情。

首先,CLR类型系统允许接口内的嵌套类型。完全可以创建一个C#或VB版本,或明天支持在接口内声明的接口,委托,类,结构和枚举,并且它将在现有CLR上运行。

其次,我会在“C#语言为什么没有实现X功能?”这一形式的问题上给予我一贯的回击。对于X的所有值,答案是相同的。为了实现,功能必须是:思考,设计,指定,实施,测试并运送给客户。如果这六件事中的任何一件没有发生那么就没有任何特征。 功能X未实现,因为其中一个或多个事件未发生。

第三,C#编译器团队(我不再使用)不必为而不是实现功能提供任何解释。特点是花钱,预算是有限的,因此要求该功能的人有责任证明其利益与其成本合理。

第四,“为什么”这些问题难以回答,“为什么不”问题更难解决。

所以,话虽如此,我会拒绝你的问题,并将其替换为我可以回答的问题:

  

假设已向C#设计团队提出此功能请求。你会反对什么论点?

  • 该功能虽然在CLR中是合法的,但在CLS中并不合法。 C#中有许多功能在CLS中不合法,但由于CLS指南特别不在接口中嵌套类型,因为大多数语言不支持,因此在C#中实现该功能本质上是鼓励人们编写不能用于其他语言的库。 建议的功能会鼓励糟糕的编程习惯

  • 嵌套类型为您提供三个主要优势。首先,他们可以访问其封闭类型的私有成员。对于没有私有成员的接口,这不是一个好处。其次,它们提供了一种方便的方法来包含外部类型的特定私有实现细节。这对于接口来说并不是一个好处,因为接口可能没有私有嵌套类型,并且根据定义没有实现细节。第三,它们提供了将一种类型与另一种类型相关联的便捷方式;但是,命名空间更好。

  • 据我所知,没有其他人要求此功能。当有很多客户需要的功能时,我们不会花钱购买任何人都不想要的功能。

  • 实现该功能并不会使语言本身更强大或更具表现力。

  • 实现这个功能并不是我所知道的一些更棒的功能的垫脚石。该功能不与任何其他“主题”相关联。这是一个“完成主义”功能,消除了一个小的非正交性,而不是一个有用的功能。

  • 缺少该功能有一个简单的解决方法;只需使嵌套类型成为顶级类型即可。

反对的情况。没有人推进 该功能的案例,它不会在设计委员会会议上持续超过五分钟的时间。您是否愿意推进该功能的案例?

答案 1 :(得分:3)

想补充一点,从C#8.0开始,允许接口使用嵌套类型。

Default interface methods - C# 8.0 specification proposals | Microsoft Docs(强调)

接口的语法已扩展为允许

  • 成员声明,​​用于声明常量,运算符,静态构造函数和嵌套类型;

所以下面的内容现在是法律

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

在其他答案中是否讨论了这是否是一种好习惯,但我个人发现特定于接口的枚举有其用途。

有趣的是,尽管此更改被列为默认接口实现的一部分,但大多数更改都需要新的运行时,即.NET Core 3.0 / .NET Standard 2.1和更高版本,具有嵌套类型但没有任何实现的接口确实可以编译并且可以只要使用支持C#8.0中编译的Roslyn CSC,就可以在.NET Framework 4.8中使用。

我认为这是由于CLR一直以来都在接口中支持嵌套类型,正​​如Eric Lippert在此处的答案中所述。

答案 2 :(得分:2)

为什么嵌套类型有意义只有几个原因。主要原因是将它们定义为私有,以便只有容器类才能访问它们。容器类将在其自己的实现中使用这些私有类型。

由于接口不是实现,因此没有充分的理由在其中嵌套类型。这将毫无用处。这就像一个农民试图用小猫帮他耕田。从理论上讲,这至少可以尝试,但它不会起任何实际作用。

查看提供的代码,我建议将Connection类升级为顶级类型。如果要根据功能组织类型,那就是命名空间的用途。在项目中创建类型组织方式的文件夹结构,并更改名称空间以反映该类型。

答案 3 :(得分:1)

从C#规范,第13.2节:

  

接口不能包含常量,字段,运算符,实例构造函数,析构函数或类型,接口也不能包含任何类型的静态成员。

嵌套类型是一种静态成员,因此不允许嵌套类型比使它们成为特殊情况更加一致。

答案 4 :(得分:1)

虽然在这篇文章的评论中提到过,但我想在此重申你可以使用VB.Net创建嵌套类型的接口,然后使用该接口及其所有嵌套类型不受限制C#。此外,您可以使用ILDasm或ILSpy等工具将该代码导出到IL,然后使用Visual Studio ILSupport扩展将其编织回您的库(前提是您实际上并不需要直接在库中)。

至于有人想要这样做的原因,这里有两个。第一种是为一组耦合接口提供蓝图/模式定义,以及支持通用概念所需的共享泛型类型参数,例如实体管理。例如:

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);
    }

}

上面也可以通过在容器接口之外的每个嵌套接口/类上重复泛型类型参数和约束来解决,但这很快就会变得混乱。另一个很好的选择是如果.Net支持名称空间上的泛型类型参数,但遗憾的是目前不支持。

在接口中嵌套类型的第二个原因是提供用于定义复合接口的组合/ mixin接口和提供接受mixin实例以构造实现复合接口和转发的新动态类的嵌套支持组合器类。调用mixins。如下:

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

    }

}

然后,您可以创建扩展上述IComposite接口的接口,该接口已经包含适用于扩展接口的合适的编写器。请注意,您可能还有其他IComposite接口变体以支持更高的mixin计数(类似于Func和Action,因为.Net目前不支持可变参数类型参数)。

您可以像以下示例一样使用它:

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";
        }
    }

}

虽然可以在IComposite界面之外创建composer类,但发现其组合支持或意图并不像看到Composer出现在IntelliSense中那么容易。在这种情况下,嵌套的Composer类实际上用于为接口提供静态方法(Create)(这是另一个不受支持的选项)。