C#接口的非公共成员

34
在C#中,当您实现接口时,所有成员都会隐式公开。如果我们能够指定访问修饰符(protected, internal, 除了private),那不是更好吗?或者我们应该使用抽象类替代?

9
如果你显式地实现API,就可以从API中隐藏接口。 - epitka
可能会在C# 8.0中出现 https://jeremybytes.blogspot.com/2019/11/c-8-interfaces-public-private-and.html - Doug Domeny
9个回答

36

如果一个接口是internal的,那么它的所有成员将对该程序集内部可见。如果一个嵌套接口是protected的,那么只有外部类的子类可以访问该接口。

对于一个接口而言,其在声明程序集之外的internal成员是没有意义的,同样声明在外部类中的protected成员也是如此。

接口的目的是描述实现类型和接口用户之间的协定。外部调用者不会关心,并且不应该强制关注实现细节,这正是internal和protected成员派上用场的地方。

对于被基类调用的protected成员,抽象类是指定基类和从中继承的类之间协定的理想方式。但在这种情况下,实现细节通常非常重要,除非它是一个退化的纯抽象类(其中所有成员都是抽象的),在这种情况下,protected成员是无用的。在这种情况下,使用接口,为实现类型提供单一的基类选择。


1
另外我可以补充一点,接口强制应用程序结构(其中的类)遵循一种模式/共性,即使您不将接口暴露给外部客户端也是一个重要的点……对吧? - PositiveGuy
我认为接口拥有“internal”成员是很有用的。如果程序集Foo包含一个带有“internal”成员的接口“IWoozle”,并且它包含五个“IWoozle”的实现,那么任何地方的代码都可以传递“IWoozle”的实例,并使用其公共成员,但接收“IWoozle”的代码将知道它是在程序集Foo中实现的。例如,考虑假设接口“IImmutableWoozle”。实现可能不会共享一个公共基类型,但如果没有实现能够存在于Foo之外,那么... - supercat
在Foo中编写代码,或者相信Foo不会做任何危险的事情,可以确保IImmutableWoozle不会成为一些古怪的可变实现。 - supercat
在这种情况下,您可以创建一个内部接口IInternalWoozle,该接口实现了IWoozle,并且Foo中使用任何IWoozle的代码都可以检查是否还实现了IInternalWoozle以便访问内部成员。.NET框架在某些地方就是这样做的。 - Mark Cidade
如果一个接口是 internal 的,那么它的所有(public)成员都不会是 internal 的。即使是这样,那也破坏了 public 的本质! - nawfal
显示剩余4条评论

22

通过在方法名前明确声明接口名称,可以隐藏接口实现:

public interface IInterface {
    public void Method();
}

public class A : IInterface {
    public void IInterface.Method() {
        // Do something
    }
}

public class Program {
    public static void Main() {
        A o = new A();
        o.Method(); // Will not compile
        ((IInterface)o).Method(); // Will compile
    }
}

2
只是一条注释:现在你不能在接口成员或显式实现的接口方法中使用 public 修饰符。例如,为了使你的代码能够在 .NET 4.5 中工作,只需删除两个 public 即可。 - Andrew
安德鲁,你指的是哪两个公共的? - Kyle Delaney
为什么 o.Method() 不能编译?有人能解释一下吗?为什么声明接口名称会隐藏接口实现? - Kyle Delaney
Kyle。这种符号(使用接口名称实现方法)被称为显式接口实现,我认为它的目的是隐藏方法不被外部调用(除非显式转换然后调用)。这对某些情况非常有用。例如,基类可能在接口方法的实现中执行一次性操作。如果该方法对外部调用者可见,则他们可能会多次调用该方法并破坏基类的工作状态。 - Xtro

5

这样做没有意义。接口是与公众签订的合同,您支持这些方法和属性。最好使用抽象类。


6
对于接口来说,一个“内部”访问修饰符似乎是非常有用的;如果任何接口成员带有这种修饰符,则只能由声明它的程序集中的代码实现,但可以被任何地方的代码使用。我可以看到它的很多用途。 - supercat
1
我认为接口并不是用作同一程序集中代码的强类型指南或脚手架的。我相信它们的主要目的是与外部世界进行“接口”。 - Ishmaeel
假设一个程序集定义了BankCheckingAccount,CreditUnionCheckingAccount,BankSavingsAccount和CreditUnionSavingsAccount。银行账户具有信用合作社账户所缺乏的功能,反之亦然。支票账户具有储蓄账户所缺乏的功能,反之亦然。对于支票账户、储蓄账户、银行账户和信用合作社账户都有类型将会很有帮助,但其中两个只能使用抽象类。如果抽象类的继承被限制在同一个程序集中... - supercat
可以确定的是CheckingAccount要么是BankCheckingAccount或CreditUnionCheckingAccount,而不是JoeHackerPhonyCheckingAccount。能够为IBankAccount接口(由BankCheckingAccount和BankSavingsAccount实现)或ICreditUnionAccount接口(由CreditUnionCheckingAccount和CreditUnionSavingsAccount实现)提供类似的保证将是有帮助的。当然,这只是一个比喻,我们并不会把真实的财务信息放在类级别的安全中来进行管理。 - supercat
“接口是与公众的契约,表明您支持这些方法和属性。” - 我不同意这个说法,我认为接口应该是(或者应该是)与实现者的契约。并且我应该能够强制执行某些私有方法和属性的实现。 - Gavin Williams
你还需要一个大个子带着棒球棍来逼我实现那个接口。我只会写一个方法桩来让编译器不报错,然后将我的实际实现放在另一个私有方法中——出于报复心理,你知道的。 - Ishmaeel

5
所有的答案都或多或少地说道这就是接口的本意,它们是通用的公共规范。

作为最受讨论的主题,让我发表两个我在SO上发现的优秀答案,当我想起这个问题时。

这个答案举了一个例子,说明派生类中非统一访问限定符的接口成员可能是无意义的。代码总比技术描述好。

对我来说,强制公开接口成员最糟糕的事情是接口本身可以是程序集内部的,但它所暴露的成员必须是公共的。 Jon Skeet在这里解释了这是设计上的遗憾

这引出了一个问题,为什么接口没有被设计成具有成员的非公共定义。这可以使合同更加灵活。在编写不希望将类的特定成员暴露给程序集外部的程序集时,这非常有用。我不知道为什么。


2
您可以隐藏几乎所有通过接口实现的对外部程序集的代码。
interface IVehicle
{
    void Drive();
    void Steer();
    void UseHook();
}
abstract class Vehicle  // :IVehicle  // Try it and see!
{
    /// <summary>
    /// Consuming classes are not required to implement this method.
    /// </summary>
    protected virtual void Hook()
    {
        return;
    }
}
class Car : Vehicle, IVehicle
{
    protected override void Hook()  // you must use keyword "override"
    {
        Console.WriteLine(" Car.Hook(): Uses abstracted method.");
    }
    #region IVehicle Members

    public void Drive()
    {
        Console.WriteLine(" Car.Drive(): Uses a tires and a motor.");
    }

    public void Steer()
    {
        Console.WriteLine(" Car.Steer(): Uses a steering wheel.");
    }
    /// <summary>
    /// This code is duplicated in implementing classes.  Hmm.
    /// </summary>
    void IVehicle.UseHook()
    {
        this.Hook();
    }

    #endregion
}
class Airplane : Vehicle, IVehicle
{
    protected override void Hook()  // you must use keyword "override"
    {
        Console.WriteLine(" Airplane.Hook(): Uses abstracted method.");
    }
    #region IVehicle Members

    public void Drive()
    {
        Console.WriteLine(" Airplane.Drive(): Uses wings and a motor.");
    }

    public void Steer()
    {
        Console.WriteLine(" Airplane.Steer(): Uses a control stick.");
    }
    /// <summary>
    /// This code is duplicated in implementing classes.  Hmm.
    /// </summary>
    void IVehicle.UseHook()
    {
        this.Hook();
    }

    #endregion
}

这将测试代码。

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car();
        IVehicle contract = (IVehicle)car;
        UseContract(contract);  // This line is identical...
        Airplane airplane = new Airplane();
        contract = (IVehicle)airplane;
        UseContract(contract);  // ...to the line above!
    }

    private static void UseContract(IVehicle contract)
    {
        // Try typing these 3 lines yourself, watch IDE behavior.
        contract.Drive();
        contract.Steer();
        contract.UseHook();
        Console.WriteLine("Press any key to continue...");
        Console.ReadLine();
    }
}

2
一个接口是所有实现类都必须遵守的契约。这意味着它们必须全部遵守,或者全部不遵守。
如果接口是公共的,则该接口的每个部分都必须是公共的,否则它对于友好/内部类和其他所有类来说意义不同。
要么使用抽象基类,要么(如果可能且实际)在接口上使用内部扩展方法

如果声明一个公共抽象类,其所有成员和构造函数都是“internal”,那么外部代码可以从程序集引用中接收到该类型的东西,并将这些引用传递回程序集的方法,同时始终保持类型安全,而无需外部代码了解有关该类的任何信息。不幸的是,如果抽象类的任何具体派生需要继承不从该抽象类派生的任何内容,则这种方法只能起到作用。 - supercat
如果允许包含内部成员的接口,它将非常类似于前面提到的抽象类,但具有优势,即可以由不派生自其他类的类实现。 - supercat

1

接口在其方法中没有访问修饰符,使它们开放给适当的访问修饰符。这是有目的的:它允许其他类型推断出遵循接口的对象可用的方法和属性。给它们受保护/内部访问器会破坏接口的目的。

如果您坚持认为需要为方法提供访问修饰符,则要么将其从接口中省略,要么像您所说的那样使用抽象类。


允许其他(外部)类型推断对象可用的方法和属性是否可用是一个受欢迎的选择吗?我认为对象应该能够决定外部世界可以推断出什么。 - nawfal
准确地说,nawfal - 接口是一种契约,而不是一个对象。接口应该用于描述可用的行为,而不是实现细节的定义。 - Jon Limjap
Jon,今天我意识到这是一个合同。不仅仅是一个合同,更像是“全世界的公共规范”。我一直在质疑这个原则,但当人们用语言表达时,他们听起来像是“为什么接口成员是公共的?”常见的答案是“这是那种合同”。@Jon Skeet 在这里说就是这样,因为它就是这样。这里是拥有非公共接口成员的另一个含义。 - nawfal
但是现在我想知道为什么接口中的成员定义不能是私有的、公共的等等。直到今天,我认为接口的存在也是为了强制类设计(其中包括公共、受保护、私有等成员),并且仍然觉得应该有一些东西来强制执行它。 - nawfal
晚回答了,但是想象一下在公共接口中有私有成员。如果一个类甚至不知道它们的存在,那么该类应该如何实现接口的所有成员呢?当然,这类似于其他修饰符。正如已经接受的答案中所指出的那样,您可以限制对接口本身的访问,防止在不应该的情况下尝试实现合同。换句话说,私有/受保护/内部接口就像其他人不知道的合同。具有非公共成员的接口就像不明确的合同,可能无法完全实现。 - user3613916

0
在我看来,这违反了封装性。我必须将一个方法实现为公共的,然后再实现一个接口。我认为没有理由强制要求在实现接口的类中使用公共访问修饰符。(c#)

0

我比较熟悉Java而不是C#,但是为什么你想在接口中使用私有成员变量呢?它没有任何实现并且对于实现类来说是不可见的,所以是无用的。接口存在的目的是为了指定行为。如果你需要默认行为,那么请使用抽象类。


1
一个内部成员有意义,对吧? - nawfal
我认为可以将整个接口(而不是单个方法)声明为 internal。这更有意义。 - Jon Limjap
1
@JonLimjap 可以实现,唯一的好处是将接口从外部程序集中隐藏,但它们仍需要其成员为公共的(这很奇怪),这意味着这些公共属性和方法对外部世界可见!! - nawfal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接