C#中"internal"关键字的实际用途

499

您能解释一下C#中internal关键字的实际用途吗?

我知道internal修饰符限制了对当前程序集的访问,但在什么情况下应该使用它?

22个回答

433

实用程序或帮助类/方法,您希望从同一程序集中的许多其他类中访问,但您想确保其他程序集中的代码无法访问。

来自MSDN(通过archive.org):

在基于组件的开发中,内部访问的常见用法是使一组组件能够以私有方式协作,而不会暴露给应用程序代码的其余部分。例如,用于构建图形用户界面的框架可以提供Control和Form类,这些类使用具有内部访问权限的成员进行协作。由于这些成员是内部的,因此它们不会暴露给正在使用框架的代码。

您还可以将internal修饰符与InternalsVisibleTo程序集级别属性一起使用,以创建被授予对目标程序集内部类特殊访问权限的“友好”程序集。

这对于创建允许调用要测试的程序集的内部成员的单元测试程序集非常有用。当然,没有其他程序集被授予这种级别的访问权限,因此在发布系统时,封装性得到了维护。


9
InternalsVisibleTo 属性非常有帮助。谢谢。 - erict
42
我认为,从需要使用 internal 的那一刻起,设计上就出现了问题。在我看来,应该能够使用纯面向对象编程来解决所有问题。此时此刻,我正在查看 .NET Framework 源代码中第一百万次左右使用 internal 的位置,这妨碍了我利用他们自己使用的东西。通过使用 internal,他们创建了一个表面上模块化但在尝试隔离内容时会崩溃的整体。另外,安全性也不是一个论点,因为有反射可以解救。 - Grimace of Despair
48
让我在这里添加一个持不同意见的评论。我个人认为,在大型多项目解决方案中,“internal”作为“public”修饰符的一个非常有用的版本。将这个修饰符添加到我的子项目中的类中,可以防止其他子项目在解决方案中“随意使用”这个类,并使我能够强制正确使用我的库。如果以后需要进行一些重构,我就不必问自己:“等等,那个人为什么要创建 Point 类的实例,而我只需要在特定计算中用于自己的目的?” - KT.
9
换句话说,假如每个人都依赖于 SmtpClient 的内部机制,并在某一时刻发现了一个 bug,.NET将会严重受阻,甚至可能长时间无法解决这个问题。我记得曾经读过一篇有趣的文章,描述了 Windows 开发人员为了保持与所有利用各种内部特性和漏洞的旧程序的“bugwards兼容性”而不得不付出的努力。在.NET中重复此错误是没有意义的。 - KT.
20
我认为 internal 比 public 更有用。只有当你明确地说“嘿,用户-我正在提供一个有用的函数供你使用”时,才会使用 public。如果你正在编写一个应用程序而不是用户库,那么所有东西都应该是 internal,因为你不打算给外部调用者提供任何函数。如果你正在编写一个用户库,那么只有你预计要向用户公开、维护、支持并一直保留到最后的有用函数才应该是 public。否则,就将其设为 internal。 - Darrell Plank
显示剩余5条评论

104
如果Bob需要BigImportantClass,那么他需要让A项目的负责人承诺书,保证BigImportantClass将按照他的需求进行编写、测试、文档化,并且制定一个流程来确保它永远不会被更改而不再满足他的需求。如果一个类是内部的,则不必经过这个过程,这可以为项目A节省预算,以便他们可以用于其他事情。内部的重点不在于让Bob的生活变得困难,而是可以控制项目A所做出的昂贵承诺的特性、寿命、兼容性等。

8
完全明白。只希望我们有能力覆盖内部成员,因为我们都是自愿的成年人。 - cwallenpoole
9
@EricLippert,不完全是我的意思。如果我继承了带有“internal”关键字的已编译代码,我就会陷入困境,对吧?除非我使用反射,否则没有办法简单地告诉编译器,“不,那个开发人员对你撒谎了。相信我就好”。 - cwallenpoole
13
如果你使用的是由编写代码不诚实的人编写的代码,并且你不喜欢这些代码,那么请停止使用他们的代码,自己编写你更喜欢的代码。 - Eric Lippert
5
@cwallenpoole说:“我一点也不生气。如果你发现自己陷入那种不幸的境地,我提供了很好的建议。” - Eric Lippert
5
@亚当: 可修改、可扩展和可定制的代码是需要花费金钱特性。如果将所有内容都公开,你不会免费获得这些特性,反而会导致更多问题;现在库不再维护其不变量,并且违反不变量被称为“错误”。如果你对库供应商有问题,请向该供应商投诉,而不是向我投诉。 - Eric Lippert
显示剩余4条评论

70

使用 internal 的另一个原因是,如果您混淆二进制文件,则混淆器知道可以安全地对任何内部类的类名进行混淆,而不能对公共类的名称进行混淆,因为这可能会破坏现有的引用。


6
看着反汇编的字节码,仍然可以很清楚地知道代码的作用。混淆器对于真正试图进入内部的人来说几乎没有什么威慑力。从未听说过一个黑客因为没有得到任何有用的函数名称而停止攻击。 - Nyerguds
46
所以你睡觉时不锁门,因为你从未听说过有盗贼被锁阻拦过? - Sergio
9
这就是为什么我在源代码中混淆类名和变量名。你也许能够理解PumpStateComparator是什么,但你能猜出LpWj4mWE代表什么吗?这是为了保住我的工作(我不这样做,请不要模仿互联网上的人)。 - Slothario

40

如果你正在编写一个将大量复杂功能封装到简单公共API中的DLL,那么“internal”关键字可用于不希望公开的类成员。

隐藏复杂性(即封装)是优质软件工程的主要概念。


21

当你在构建非托管代码的包装器时,通常会使用internal关键字。

如果你想要DllImport一个基于C/C++库的函数,你可以将这些函数作为类的静态函数导入,并使它们internal,这样用户只能访问你的包装器,而不是原始API,从而避免对程序造成影响。由于这些函数是静态的,所以可以在程序集中的多个包装器类中重复使用。

你可以看一下Mono.Cairo,它是一个使用这种方法的cairo库的包装器。


16

在“尽可能使用严格的修改器”规则的驱动下,我在需要从另一个类访问方法的任何地方都使用internal,直到我明确需要从另一个程序集访问它。

由于程序集接口通常比其类接口更窄,因此我在许多地方使用它。


3
如果你“尽可能使用严格的修饰符”,为什么不使用private呢? - R. Martinho Fernandes
7
当类的属性/方法需要在类外部被访问时,private 访问权限太严格了,而且需要从另一个程序集访问的情况并不经常发生。 - Ilya Komakhin

12

我认为过度使用internal是不好的。你不应该将某些功能仅暴露给某些类,而不是其他消费者。

在我看来,这会破坏接口和抽象层。这并不意味着它永远不应该使用,但更好的解决方案是重构为不同的类,或者在可能的情况下以不同的方式使用。然而,并非总是可行。

原因在于另一个开发人员可能负责在与您的程序集相同的程序集中构建另一个类。内部成分减少了抽象的清晰度,并且如果被误用就会引起问题。这与将其设置为public一样。正在由其他开发人员构建的另一个类仍然是消费者,就像任何外部类一样。类的抽象化和封装不仅仅是为了保护/来自外部类,而是适用于所有类。

另一个问题是许多开发人员会认为他们可能需要在程序集的其他地方使用它,并在此时将其标记为internal,即使他们并不需要它。然后,另一个开发人员可能会认为它可以使用。通常情况下,你希望将其标记为private,直到你有明确的需求。

但其中一些因素可能是主观的,我并不是说它永远不能使用。只有在需要时才使用。


我经常为域对象构建成员,这些成员应该对同一程序集中的其他域对象公开。然而,这些相同的成员不应该对程序集外部的客户端代码可用。对于这种情况,“internal”非常适合。 - Rock Anthony Johnson

11

这个例子包含两个文件:Assembly1.cs和Assembly2.cs。第一个文件包含一个内部基类BaseClass。在第二个文件中,试图实例化BaseClass会产生一个错误。

// Assembly1.cs
// compile with: /target:library
internal class BaseClass 
{
   public static int intM = 0;
}

// Assembly1_a.cs
// compile with: /reference:Assembly1.dll
class TestAccess 
{
   static void Main()
   {  
      BaseClass myBase = new BaseClass();   // CS0122
   }
}
在这个例子中,使用与示例1相同的文件,并将BaseClass的可访问级别更改为public。还将成员IntM的可访问级别更改为internal。在这种情况下,您可以实例化该类,但无法访问内部成员。
// Assembly2.cs
// compile with: /target:library
public class BaseClass 
{
   internal static int intM = 0;
}

// Assembly2_a.cs
// compile with: /reference:Assembly1.dll
public class TestAccess 
{
   static void Main() 
   {      
      BaseClass myBase = new BaseClass();   // Ok.
      BaseClass.intM = 444;    // CS0117
   }
}

来源: http://msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx


9
我前几天在一个我记不起来的博客上看到了一个有趣的例子,大概是关于如何让一个抽象类在另一个程序集中可见但不能被继承。虽然这个想法不是我自己提出来的,但我认为它可能很有用。
假设你想让一个抽象类在另一个程序集中可见,但你又不希望其他人可以从它继承。使用 sealed 关键字是行不通的,因为这个类是抽象的,而且该程序集中的其他类确实从它继承。使用 private 关键字也不行,因为你可能需要在另一个程序集中声明一个 Parent 类。
namespace Base.Assembly
{
  public abstract class Parent
  {
    internal abstract void SomeMethod();
  }
// 这样做没有问题,因为 ChildWithin 和 Parent 在同一个程序集中。 public class ChildWithin : Parent { internal override void SomeMethod() { } } }
namespace Another.Assembly { // 会出现错误,因为你无法重写一个 internal 方法。 public class ChildOutside : Parent { }
public class Test {
// 没问题 private Parent _parent;
public Test() { // 仍然没问题 _parent = new ChildWithin(); } } }
如你所见,这种方法可以让别人使用 Parent 类,但不能从它继承。

7

当您有需要在当前程序集范围内访问但从不在其外部访问的方法、类等时。

例如,一个数据访问层(DAL)可能有一个ORM,但对象不应该暴露给业务层,所有交互都应通过静态方法和传递所需参数来完成。


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