为什么在内部类中使用公共方法?

318

我们项目中有很多这样的代码:

internal static class Extensions
{
    public static string AddFoo(this string s)
    {
        if (s == null)
        {
            return "Foo";
        }

        return $({s}Foo);
    }
}

除了“以后更容易将类型设置为公共类型”之外,是否还有任何显式原因这样做?

我怀疑只在非常奇怪的边缘情况(Silverlight中的反射)下才会有影响,或者根本没有影响。


1
根据我的经验,这是正常的做法。 - phoog
6
好的,但为什么这是常规做法?也许只是比将一堆方法更改为internal更容易?快速解决方案-->将类型标记为internal而不是更改方法。 - bopapa_1979
这一直是我所假设的。但是,我没有一个深思熟虑的分析,这就是为什么我没有发表答案的原因。 - phoog
2
Eric Lippert的回答很好地概括了我的想法,并且提出了一些一直在我脑海中潜伏着的东西:接口成员的隐式实现必须是公共的。 - phoog
这个方法不是等同于 return s + "Foo"; 吗? + 运算符不关心 null 或空字符串。 - Mikkel R. Lund
12个回答

513

更新:此问题是我在2014年9月的博客中讨论的主题。感谢这个好问题!

即使在编译器团队内部,对于这个问题也存在着相当大的争议。

首先,明智的做法是理解规则。类或结构体的公共成员是可以被能够访问包含类型的任何内容所访问的成员。因此,内部类的公共成员实际上是内部的。

那么现在,对于一个内部类,你希望在程序集中访问它的成员,应该将这些成员标记为public还是internal呢?

我的意见是:将这样的成员标记为public。

我使用“public”表示“此成员不是实现细节”。受保护的成员是实现细节;有关它的某些内容将需要使派生类正常工作。内部成员是实现细节;该程序集中的其他内容需要该成员才能正常工作。公共成员表示“此成员代表对象提供的文档功能的关键部分”。

基本上,我的观点是:假设我决定将这个内部类变成公共类。为了做到这一点,我希望只改变一个事情:类的可访问性。如果将内部类变成公共类意味着我还必须将内部成员变成公共成员,那么该成员就是类的公共表面区域的一部分,应该在一开始就是公共的。

其他人则持不同意见。有一些人认为他们想要能够瞥一眼成员的声明,并立即知道它是否仅从内部代码中调用。

不幸的是,这并不总是很好地奏效;例如,实现内部接口的内部类仍然必须将实现成员标记为public,因为它们是类的公共表面的一部分


15
我喜欢这个答案...它很符合我对编写尽可能自我记录的代码的态度,而作用域是传达意图的好方法,特别是如果你的其他团队成员理解你的惯例。 - bopapa_1979
11
谢谢夸奖。你的意思是我表达了你想要表达的,但我的表述更好(同时也提到了接口方面的问题,尽管这只适用于隐式成员实现)。 - phoog
2
接口部分非常重要 - 使用 internal 实现接口方法是不可能的,需要使用 public 或显式接口声明。因此,public 这个词具有两个含义的重载。 - Michael Stum
9
从理论的角度来看,你对“公共”的支持论点非常有说服力。然而,“并不总是能很好地运作”这一点尤其强有力。失去一致性会削弱任何“一目了然”的好处。以这种方式使用“内部”,意味着故意建立有时是错误的直觉。在我的看法中,拥有大多数正确的直觉是编程中可怕的事情。 - Brian
3
博客文章中的重要引言:“我的建议是与您的团队讨论问题,作出决策,然后坚持执行。” - bopapa_1979
这是否意味着您永远不会在内部类中拥有内部成员?还是说在某些情况下,如果稍后将类本身公开,类的特定成员应保持为内部成员? - comecme

23
如果一个类是internal,那么从可访问性的角度来看,你把方法标记为internal或者public都无所谓。然而,最好还是采用你在类为public时使用的类型。
有人认为这样可以方便从internal转换为public。同时,这也作为该方法描述的一部分。Internal方法通常被认为是不安全的,不能随意访问,而public方法则被认为是(大多数情况下)可以自由调用的。
通过像在public类中一样使用internalpublic,你可以确保你正在传达所期望的访问权限等级,并且可以减少将来使该类变为public所需的工作量。

我倾向于在创建良好的API并允许我的预期消费者执行我想要他们执行的操作的同时,尽可能地限制一切。我也同意您的看法,即如果该类是公共的,则将成员标记为我所做的那样。更具体地说,只有当我认为成员始终安全时,才会将任何成员标记为公共的。否则,稍后有人将类标记为公共(这种情况确实发生过)可能会暴露非安全成员。 - bopapa_1979
@Eric:如果你想在准备好之前放弃确定方法的安全性,那么也可以,但我只是指那些被认为是安全的方法,这种情况下就不会有暴露。 - Guvante

11

我猜想"后来将类型公开更容易?" 是指这个。

作用域规则意味着该方法只能作为internal可见 - 因此,方法是否标记为publicinternal并不重要。

一个可能的情况是该类最初为公共类,后来更改为internal,开发人员没有费心去更改所有方法的可访问性修饰符。


2
+1 对于显而易见的“public class 变成了 internal”情况的把握。 - bopapa_1979

11

我经常将内部类中的方法标记为public而不是internal,原因是a)这并不重要,b)我使用internal表示该方法有意被设为内部方法(即我有一些原因不想在公共类中暴露此方法)。因此,如果我有一个内部方法,我真的必须理解它为什么是内部的,然后才能将其更改为public,而如果我在处理内部类中的公共方法,则必须考虑类为什么是内部的,而不是每个方法为什么是内部的。


1
谢谢你的回答。虽然我倾向于固执地坚持编码时“一切”都很重要 :) - bopapa_1979
2
你说得对,Eric。那只是一句随口说的话。希望我的回答对你有所帮助。我认为Eric Lippert的回答表达了我想要表达的意思,但他解释得更好。 - Ɖiamond ǤeezeƦ

9

在某些情况下,可能是内部类型实现了公共接口,这意味着在该接口上定义的任何方法仍需要声明为公共。


2
出于与在任何其他类中使用公共方法的原因相同 - 以便它们对包含类型的外部是公共的。
类型的访问修饰符与其成员的访问修饰符没有任何关系。这两个决策完全独立。
仅因为某些类型和成员修饰符的组合产生了表面上(或者正如其他人所称的“有效地”)相同的结果,并不意味着它们在语义上相同。
实体的本地访问修饰符(如在代码中声明的)和其全局有效访问级别(通过包含链路进行评估)也是完全不同的东西。锁定建筑物内的开放式办公室仍然是开放的,即使你不能从街上真正进入它。
首先考虑你在本地需要什么,而不要考虑最终效果。
- Public's Public:经典情况。 - Public's Internal:类型是公共的,但您想在程序集中进行一些半法律的访问来做一些hacky-wacky的事情。 - Internal's Public:您隐藏整个类型,但在程序集内部它具有经典的公共界面 - Internal's Internal:我无法想出任何现实世界的例子。也许很快就会变成public's internal?
Internal's Public与Internal's Internal之间的抉择是一个错误的二元论。这两者具有完全不同的含义,并且应该在各自的情况下使用,不重叠。

另一种思考方式。我从被接受的答案中得到了我需要的,但这也很有用。“首先考虑你需要本地的东西。”明白。 - bopapa_1979

2

这个和以前的一样,公用方法会被标记为内部的,因为它在一个内部类里面,但是它有一个优势(你已经猜到了),如果你想将类标记为public,你只需要改动少量代码。


3
已经有一个相关问题:https://dev59.com/XXRB5IYBdhLWcg3wH0WW - Mario Corchero

1
这与你的例子无关,但对于在C#中实现接口的类(包括7.3版本),它是相关的。
internal interface IAmOnlyInternalViewable
{
    void CallMeOnlyInsideAssembly();
    //internal void CallMeOnlyInsideAssembly();  // <-- is not possible up to C# 7.3 inclusive
}

internal class OnlyInternalVisible : IAmOnlyInternalViewable
{
    public void CallMeOnlyInsideAssembly() { } //Must be public to implement the interface
}

所以,如果你想要有一个实现接口的内部类,那么实现的方法需要是公共的。

0
今天我遇到了这个问题。直到现在,如果类是internal的,我会说所有方法都应该标记为internal,并且认为其他任何情况都是糟糕的编码或懒惰,特别是在企业开发中;然而,我不得不子类化一个public类并覆盖其中的一个方法:
internal class SslStreamEx : System.Net.Security.SslStream
{
    public override void Close()
    {
        try
        {
            // Send close_notify manually
        }
        finally
        {
            base.Close();
        }
    }
}

该方法必须是 public,这让我开始思考:除非确实需要,否则没有设置方法为 internal 的逻辑点,正如 Eric Lippert 所说。
直到现在,我从未真正停下来思考过它,我只是接受了它,但在阅读了 Eric 的文章后,它真的让我深思熟虑,并经过了很多斟酌后,它变得很有道理。

0

我对此有额外的看法。起初,我在想如何在内部类中声明公共内容是有意义的。然后我来到这里,阅读了一些文章,发现如果您稍后决定将该类更改为公共类,则这样做可能是有好处的。确实如此。因此,在我的脑海中形成了一个模式如果它不会改变当前行为,则要宽容,并允许那些在当前代码状态下没有意义(并且不会伤害)但稍后会伤害的事情,如果您更改类的声明。

就像这样:

public sealed class MyCurrentlySealedClass
{
    protected void MyCurretlyPrivateMethod()
    {
    }
}

根据我上面提到的“模式”,这应该是完全没问题的。它遵循相同的思路。它表现得像一个私有方法,因为你不能继承这个类。但如果你删除了sealed约束条件,它仍然是有效的:继承的类可以看到这个方法,这正是我想要实现的。但你会收到一个警告:CS0628或CA1047。它们都是关于不要在sealed类中声明受保护成员的。此外,我发现完全一致的意见,即它是毫无意义的:'Protected member in sealed class' warning (a singleton class) 所以,在收到这个警告和链接的讨论后,我决定将所有内容都设为internal或更少,在一个internal类中,因为它更符合那种思维方式,我们不会混合不同的“模式”。

我非常喜欢按照Eric Lippert所说的方式去做(或至少是出于他所列举的原因)。他的文章绝对值得一读。 - bopapa_1979

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