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

Posted

技术标签:

【中文标题】C# 中“internal”关键字的实际用途【英文标题】:Practical uses for the "internal" keyword in C# 【发布时间】:2010-09-15 00:12:55 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

来自MSDN(来自archive.org):

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

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

这对于创建单元测试程序集很有用,然后允许调用要测试的程序集的内部成员。当然,没有其他程序集被授予此级别的访问权限,因此当您发布系统时,会保持封装。

【讨论】:

InternalsVisibleTo 属性很有帮助。谢谢。 我的感觉是,从您需要内部的那一刻起,某处的设计就有问题。恕我直言,应该能够使用纯 OO 来解决所有问题。此时此刻,我正盯着 .NET Framework 源代码中大约百万分之一的 internal 使用,阻止我利用他们自己使用的东西。通过使用 internal,他们创建了一个表面上模块化的整体,但当你尝试隔离东西时就会崩溃。此外,安全不是一个论点,因为救援需要反思。 @GrimaceofDespair:让我在这里添加一个矛盾的评论。我个人认为 internal 是大型多项目解决方案中“public”修饰符的一个非常有用的版本。将此修饰符添加到我的子项目中的类会禁止解决方案中的其他子项目“随机使用”此类,并使我可以强制正确使用我的库。如果稍后我需要进行一些重构,我不必问自己“等等,但是为什么那个人会去创建一个 Point 类的实例,我只需要在这个特定的计算中用于我自己的目的”? 换一种说法,如果每个人都依赖 SmtpClient 的内部结构,并且在某个时刻在那里发现了一个错误,那么 .NET 修复该错误会遇到严重的问题,甚至可能保持它很长一段时间。我记得我曾经读过一篇有趣的文章,其中描述了 Windows 开发人员为了与所有使用各种内部功能和错误的旧程序保持“错误兼容性”而必须付出的努力。用 .NET 重复这个错误是没有意义的。 我认为内部比公开更有用。唯一一次使用 public 是当你明确地说“这里,用户 - 我正在提供一个有用的功能供你使用”。如果您正在编写应用程序而不是用户库,那么所有内容都应该是内部的,因为您根本不想给人们任何外部调用的函数。如果您正在编写用户库,那么只有您希望提供给用户、维护、支持和保留到时间结束的有用功能才应该公开。否则,将其设为内部。【参考方案2】:

如果 Bob 需要 BigImportantClass,那么 Bob 需要让拥有项目 A 的人注册,以保证 BigImportantClass 的编写将满足他的需求,经过测试以确保它满足他的需求,并记录为满足他的需求,并且将建立一个流程,以确保它永远不会被更改,从而不再满足他的需求。

如果一个类是内部的,那么它就不必经过这个过程,这样可以节省项目 A 的预算,他们可以花在其他事情上。

内部的意义并不是让 Bob 过不下去。正是因为它允许您控制项目 A 对特性、生命周期、兼容性等做出的昂贵承诺。

【讨论】:

非常有意义。只是希望我们有能力覆盖内部成员,因为我们都是在这里同意的成年人。 @EricLippert 不是我的意思。如果我继承其中包含“内部”的编译代码,我会被卡住,对吗?除非我使用反射,否则无法简单地告诉编译器“不,其他开发人员对你撒谎。你应该相信我”。 @cwallenpoole:如果您使用的代码是由编写您不喜欢的代码的骗子编写的,那么请停止使用他们的代码并编写您自己更喜欢的代码。 @EricLippert 那应该是在开玩笑,我不是故意冒犯的。 (在这种情况下,我主要是想确认我是 SOL)。 @cwallenpoole:我一点也不生气。如果您发现自己陷入这种不幸的境地,我会提供很好的建议。【参考方案3】:

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

【讨论】:

噗。用反汇编程序查看字节码仍然可以很清楚地知道代码的作用。混淆器对任何真正试图进入内部的人几乎没有温和的威慑力。从来没有听说过黑客因为没有得到任何有用的函数名而停止。 所以你睡觉时不锁门,因为你从没听说过有小偷被锁拦住? 这就是我在源代码中打乱类和变量名的原因。您也许能够弄清楚 PumpStateComparator 是什么,但您能猜出 LpWj4mWE 是什么吗? #jobsecurity(我不这样做,请不要真的这样做,互联网人。)【参考方案4】:

如果您正在编写一个将大量复杂功能封装到一个简单的公共 API 中的 DLL,那么“内部”用于不公开公开的类成员。

隐藏复杂性(也称为封装)是质量软件工程的主要概念。

【讨论】:

【参考方案5】:

在非托管代码上构建包装器时会大量使用 internal 关键字。

如果您有一个基于 C/C++ 的库并希望 DllImport 导入,您可以将这些函数作为类的静态函数导入,并使其成为内部函数,因此您的用户只能访问您的包装器而不是原始 API,因此不能乱来。对于您需要的多个包装类,您可以在程序集中的任何地方使用静态函数。

您可以查看 Mono.Cairo,它是使用这种方法的 cairo 库的包装器。

【讨论】:

【参考方案6】:

受“尽可能使用严​​格的修饰符”规则的驱动,我在需要访问其他类的方法(例如,在我明确需要从另一个程序集访问它)之前使用 internal。

由于汇编接口通常比其类接口的总和更窄,所以我使用它的地方很多。

【讨论】:

如果你“尽可能使用严​​格的修饰符”为什么不私有呢? 由于 private 在类属性/方法必须在类外部访问时过于严格,并且必须从另一个 asse 访问它们的情况,bly 并不常见。【参考方案7】:

我发现 internal 被过度使用了。你真的不应该只将某些功能暴露给某些你不会暴露给其他消费者的类。

在我看来,这破坏了接口,破坏了抽象。这并不是说永远不应该使用它,但更好的解决方案是重构为不同的类或尽可能以不同的方式使用。但是,这可能并不总是可行的。

它可能导致问题的原因是另一个开发人员可能负责在与您相同的程序集中构建另一个类。拥有内部会降低抽象的清晰度,并且如果被滥用可能会导致问题。如果您将其公开,这将是相同的问题。其他开发人员正在构建的其他类仍然是消费者,就像任何外部类一样。类抽象和封装不仅仅是为了保护/免受外部类的影响,而是为了任何和所有类。

另一个问题是,许多开发人员会认为他们可能需要在程序集的其他地方使用它并将其标记为内部,即使他们当时不需要它。然后,另一个开发人员可能会认为它在那里是可以接受的。通常,您希望将其标记为私有,直到您有明确的需求。

但其中一些可能是主观的,我并不是说不应该使用它。只在需要时使用。

【讨论】:

我经常构建域对象,它们的成员应该对同一程序集中的其他域对象公开。但是,这些相同的成员不应该对程序集之外的客户端代码可用。对于这种情况,“内部”非常合适。【参考方案8】:

前几天,也许是一周,在一个我记不得了的博客上看到了一篇有趣的文章。基本上我不能把这归功于它,但我认为它可能有一些有用的应用程序。

假设您希望另一个程序集可以看到一个抽象类,但您不希望有人能够从它继承。 Sealed 不起作用,因为它是抽象的,该程序集中的其他类确实从它继承。 Private 不起作用,因为您可能想在另一个程序集中的某处声明一个 Parent 类。

命名空间 Base.Assembly 公共抽象类父 内部抽象 void SomeMethod(); //这很好用,因为它在同一个程序集中。 公共类 ChildWithin : 父级 内部覆盖 void SomeMethod() 命名空间另一个.Assembly //Kaboom,因为你不能覆盖一个内部方法 公共类 ChildOutside :父 公开课测试 //正好 私人父母_parent; 公共测试() //还是可以的 _parent = new ChildWithin();

如您所见,它有效地允许某人使用 Parent 类而不能从其继承。

【讨论】:

【参考方案9】:

当你有方法、类等需要在当前程序集的范围内访问时,永远不能在它之外访问。

例如,一个 DAL 可能有一个 ORM,但对象不应暴露给业务层,所有交互都应通过静态方法完成并传入所需的参数。

【讨论】:

【参考方案10】:

此示例包含两个文件: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

【讨论】:

【参考方案11】:

internal 的一个非常有趣的用途——当然,内部成员仅限于声明它的程序集——在某种程度上从中获得“朋友”功能。朋友成员是仅对声明它的程序集之外的某些其他程序集可见的东西。 C# 没有对朋友的内置支持,但 CLR 有。

您可以使用InternalsVisibleToAttribute 声明一个友元程序集,并且友元程序集内的所有引用都会将您声明的程序集的内部成员视为该友元程序集范围内的公共成员。这样做的一个问题是所有内部成员都是可见的。你不能挑挑拣拣。

InternalsVisibleTo 的一个很好的用途是将各种内部成员公开给单元测试程序集,从而消除了测试这些成员的复杂反射工作的需要。所有内部成员都可见并不是什么大问题,但是采用这种方法确实会严重破坏您的类接口,并且可能会破坏声明程序集中的封装。

【讨论】:

【参考方案12】:

根据经验,有两种成员:

公共表面:从外部程序集可见(公共、受保护和内部受保护): 调用者不受信任,因此需要参数验证、方法文档等。 私有表面:从外部程序集中不可见(私有和内部,或内部类): caller 一般是可信的,所以参数验证、方法文档等可以省略。

【讨论】:

【参考方案13】:

降噪,你暴露的类型越少,你的库就越简单。 防篡改/安全性是另一个问题(尽管反射可以战胜它)。

【讨论】:

【参考方案14】:

内部类使您能够限制程序集的 API。这有一些好处,比如让您的 API 更易于理解。

此外,如果您的程序集中存在错误,则修复引入重大更改的可能性较小。如果没有内部类,您将不得不假设更改任何类的公共成员将是一项重大更改。对于内部类,您可以假设修改它们的公共成员只会破坏程序集的内部 API(以及 InternalsVisibleTo 属性中引用的任何程序集)。

我喜欢在类级别和程序集级别进行封装。有些人不同意这一点,但很高兴知道该功能可用。

【讨论】:

【参考方案15】:

internal 关键字的一个用途是限制程序集用户对具体实现的访问。

如果您有一个工厂或其他一些用于构造对象的中心位置,那么您的程序集的用户只需要处理公共接口或抽象基类。

此外,内部构造函数允许您控制实例化公共类的位置和时间。

【讨论】:

【参考方案16】:

我有一个项目使用 LINQ-to-SQL 作为数据后端。我有两个主要的命名空间:Biz 和 Data。 LINQ 数据模型位于 Data 中并标记为“内部”; Biz 命名空间有围绕 LINQ 数据类的公共类。

所以有Data.ClientBiz.Client;后者公开了数据对象的所有相关属性,例如:

private Data.Client _client;
public int Id  get  return _client.Id;  set  _client.Id = value;  

Biz 对象有一个私有构造函数(强制使用工厂方法)和一个内部构造函数,如下所示:

internal Client(Data.Client client) 
    this._client = client;

库中的任何业务类都可以使用它,但前端 (UI) 无法直接访问数据模型,从而确保业务层始终充当中介。

这是我第一次真正大量使用internal,事实证明它非常有用。

【讨论】:

【参考方案17】:

在某些情况下,将类成员设为internal 是有意义的。例如,如果您想控制类的实例化方式;假设您提供了某种工厂来创建类的实例。您可以将构造函数设为internal,以便工厂(位于同一程序集中)可以创建该类的实例,但该程序集之外的代码不能。

但是,我认为在没有特定原因的情况下创建课程或成员 internal 没有任何意义,就像在没有特定原因的情况下创建 publicprivate 一样有意义。

【讨论】:

【参考方案18】:

我唯一使用过 internal 关键字的是我的产品中的许可证检查代码;-)

【讨论】:

【参考方案19】:

这个怎么样:通常建议您不要将 List 对象公开给程序集的外部用户,而是公开 IEnumerable。但是在程序集中使用 List 对象要容易得多,因为您可以获得数组语法和所有其他 List 方法。因此,我通常有一个内部属性来公开要在程序集中使用的 List。

欢迎对此方法发表评论。

【讨论】:

@Samik - 您能否详细说明一下,“通常建议您不要向程序集的外部用户公开 List 对象,而是公开 IEnumerable。”为什么首选 ienumerable? @Howiecamp:主要是因为 IEnumerable 提供了对列表的只读访问权限,就像您直接公开列表一样,它可以被客户端修改(如删除元素等),这可能是意外。 此外,公开 List 或 IList 会创建一个合约,您将始终允许(例如)快速随机访问数据。如果有一天你决定改变你的方法来使用另一个集合,或者根本不使用集合(收益返回),你要么必须创建一个 List 来返回一个值,要么通过更改方法的返回类型来打破合同。使用不太具体的返回类型为您提供了灵活性。【参考方案20】:

请记住,当有人查看您的项目命名空间时,任何定义为 public 的类都会自动显示在智能感知中。从 API 的角度来看,重要的是只向项目的用户显示他们可以使用的类。使用 internal 关键字隐藏他们不应该看到的东西。

如果您的项目 A 的 Big_Important_Class 打算在您的项目之外使用,那么您不应将其标记为 internal

但是,在许多项目中,您通常会拥有实际上只打算在项目内部使用的类。例如,您可能有一个将参数保存到参数化线程调用的类。在这些情况下,您应该将它们标记为 internal,如果只是为了保护自己免受未来 API 意外更改的影响。

【讨论】:

那么为什么不使用 private 呢? private 关键字只能用于在另一个类或结构中定义的类或结构上。在命名空间中定义的类只能声明为publicinternal【参考方案21】:

这个想法是,当您设计一个库时,只有那些打算从外部(由您的库的客户)使用的类应该是公共的。这样你就可以隐藏类

    在未来的版本中可能会发生变化(如果它们是公开的,您会破坏客户端代码) 对客户无用,可能引起混淆 不安全(因此不当使用可能会严重破坏您的库)

等等

如果您正在开发内部解决方案,那么我认为使用内部元素并不那么重要,因为通常客户会不断与您联系和/或访问代码。不过,它们对于库开发人员来说相当重要。

【讨论】:

【参考方案22】:

当您添加 Web 服务引用时,您可以为生成的客户端类选择内部/公共访问。如果在示例中您不想使这些生成的类对项目外部可见但您想以某种方式包装它们,这将有所帮助。

【讨论】:

【参考方案23】:

当您的类或方法不完全适合面向对象范式时,它们会做危险的事情,需要从您控制的其他类和方法中调用,并且您不想让其他人使用。

public class DangerousClass 
    public void SafeMethod()  
    internal void UpdateGlobalStateInSomeBizarreWay()  

【讨论】:

你想说什么?这不是很清楚。至少对我来说不是。

以上是关于C# 中“internal”关键字的实际用途的主要内容,如果未能解决你的问题,请参考以下文章

在C#中internal关键字是什么意思?

在C#中internal关键字是啥意思?

C#中'in'关键字的用途是啥? [复制]

C#中Internal class与静态类说明

C# 内存泄漏之 Internal 关键词代表什么?

Java的Final和C#的Const,Readonly比较分析(转载)