C#中“内部”关键字的实际用法


Answers:


379

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

MSDN(通过archive.org):

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

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

这对于创建单元测试程序集很有用,然后允许其调用要测试的程序集的内部成员。当然,没有其他程序集被授予此访问级别,因此,在释放系统时,将维护封装。


8
InternalsVisibleTo属性是一个很大的帮助。谢谢。
2011年

33
我的感觉是,从您需要内部的那一刻起,某个地方的设计就有问题了。恕我直言,一个人应该能够使用纯OO解决所有问题。此时此刻,我正在盯着.NET Framework源代码中使用内部代码的大约一百万分之一,禁止我利用它们自己使用的东西。通过使用内部,他们创建了一个整体,表面上是模块化的,但是当您尝试隔离事物时会崩溃。同样,安全也不是争论的焦点,这是对救援的反思。
绝望的鬼脸

26
@GrimaceofDespair:让我在这里添加一个有趣的评论。我个人认为内部是大型多项目解决方案中“公共”修饰符的非常有用的版本。在子项目中的类上添加此修饰符可防止解决方案中的其他子项目“随机使用”该类,并使我有可能强制正确使用我的库。如果以后需要进行一些重构,则不必问自己“等待,但是为什么那个家伙去创建Point类的实例,而这个实例仅在我自己的特定计算中才需要”?
KT。

3
换句话说,如果每个人都依赖于SmtpClient的内部结构,并且在那儿发现一个错误,.NET将很难解决该错误,甚至可能将其保留很长时间。我记得我曾经读过一篇有趣的文章,该文章描述了Windows开发人员为了与使用各种内部功能和错误的所有较旧程序保持“错误兼容”而必须付出的努力。用.NET重复此错误没有意义。
KT。

13
我认为内部比公共更有用。唯一使用public的时间是您明确地说“用户,这里提供了有用的功能供您使用”。如果您是在编写应用程序而不是用户库,则所有内容都应该是内部的,因为您并没有试图给人们提供任何外部调用的功能。如果要编写用户库,则只有希望提供给用户的有用功能,维护,支持和保留直到结束。否则,将其设置为内部。
达雷尔·普兰克

85

如果Bob需要BigImportantClass,则Bob需要让拥有项目A的人员进行签名,以确保将BigImportantClass编写为满足他的需求,并进行测试以确保它满足他的需求,并被记录为满足他的需求,并且该过程将会就位,以确保它永远不会改变,从而不再满足他的需求。

如果一个类是内部类,则不必经过该过程,从而节省了项目A的预算,他们可以将其用于其他事情。

内部的意义不是鲍勃的生活困难。这是因为它允许您控制Project A在功能,寿命,兼容性等方面做出的昂贵承诺。


5
很有道理。只希望我们有能力超越内部成员,因为我们都同意这里的成年人。
cwallenpoole

6
@EricLippert不是我的意思。如果我继承了其中包含“内部”的编译代码,那我就被卡住了,对吗?除非我使用反射,否则无法简单地告诉编译器“不,其他开发人员对您撒谎。您应该信任我”。
cwallenpoole

11
@cwallenpoole:如果您使用的是不喜欢写代码的骗子编写的代码,请停止使用他们的代码,然后编写自己喜欢的代码。
埃里克·利珀特

2
@EricLippert那应该是面颊上的舌头,我并不是要冒犯。(在大多数情况下,我只是想确认自己是SOL)。
cwallenpoole 2013年

5
@cwallenpoole:至少我一点也不生气。如果您发现自己陷于这种不幸的境地,我会提供很好的建议。
埃里克·利珀特

60

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


4
PFF。用反汇编器查看字节码仍然可以很清楚地知道代码的作用。混淆器对真正试图进入内部的人几乎没有温和的威慑作用。从来没有听说过黑客因为没有得到任何有用的函数名称而停止。
Nyerguds

28
这样您就不会在睡觉时锁上门,因为您从未听说过小偷被锁锁住了吗?
塞尔吉奥

4
这就是为什么我在源代码中加扰类和变量名的原因。您也许可以弄清楚什么是PumpStateComparator,但是您可以猜出LpWj4mWE是什么吗?#jobsecurity(互联网人士,我不这样做,请实际上不要这样做。)
Slothario

32

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

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


20

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

当您具有要DllImport的基于C / C ++的库时,可以将这些函数作为类的静态函数导入,并使它们成为内部函数,因此您的用户只能访问包装器,而不能访问原始API,因此它不能搞砸了。这些函数是静态的,您可以在程序集中的任何地方使用它们,以获取所需的多个包装器类。

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


12

在“尽可能使用严​​格的修饰符”规则的驱使下,我在需要访问另一个类的方法的所有地方使用内部,直到我明确需要从另一个程序集访问它为止。

由于汇编接口通常比其类接口的总和还要狭窄,因此在很多地方都可以使用它。


3
如果“尽可能使用严​​格的修饰符”,为什么不私有呢?
R. Martinho Fernandes 2009年

6
由于私有在必须在类外部访问类属性/方法时以及在必须从另一个资产访问它们的情况下过于严格,因此,bly并不常见。
伊利亚·科马欣2009年

11

我发现内部使用过多。您确实不应仅将某些功能暴露于某些类,而不会暴露给其他使用者。

我认为这破坏了接口,破坏了抽象。这并不是说永远不应该使用它,而是更好的解决方案是重构到不同的类或在可能的情况下以不同的方式使用。但是,这并非总是可能的。

可能导致问题的原因是,另一个开发人员可能要负责在与您相同的程序集中构建另一个类。具有内部结构会降低抽象的清晰度,并且如果滥用会导致问题。就像您将其公开一样,这将是同样的问题。就像任何外部类一样,由其他开发人员构建的另一个类仍然是使用者。类抽象和封装并不是仅用于保护外部类,也不能用于外部类,而是用于任何和所有类。

另一个问题是,许多开发人员会认为他们可能需要在程序集中的其他位置使用它,并且无论如何都将其标记为内部,即使他们当时不需要它。然后,另一位开发人员可能会认为它值得一试。通常,您希望在没有明确需求之前将其标记为私有。

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


我经常构建域对象,这些域对象具有对于同一程序集中的其他域对象应该是公共的成员。但是,这些相同的成员不应对程序集外部的客户端代码可用。对于这种情况,“内部”非常合适。
安东尼·约翰逊

8

前几天,也许是一周,在一个我不记得的博客上看到了一个有趣的故事。基本上,我不能为此付出功劳,但我认为它可能会有一些有用的应用程序。

假设您希望一个抽象类可以被另一个程序集看到,但是您不希望某人能够从该类继承。Sealed无法工作,因为它是抽象的,这是有原因的,该程序集中的其他类确实会继承自它。私有不起作用,因为您可能想在另一个程序集中的某个地方声明一个Parent类。

名称空间Base.Assembly
{
  公共抽象类家长
  {
    内部抽象void SomeMethod();
  }

  //由于在同一程序集中,因此效果很好。
  公共类ChildWithin:父母
  {
    内部重写void SomeMethod()
    {
    }
  }
}

命名空间Another.Assembly
{
  // Kaboom,因为您无法覆盖内部方法
  公共课程ChildOutside:家长
  {
  }

  公共课测验 
  { 

    //正好
    私人父母_parent;

    公共测试
    {
      //还是不错
      _parent =新的ChildWithin();
    }
  }
}

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


7

本示例包含两个文件: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/zh-cn/library/7c5ka91b(VS.80).aspx


6

当您具有需要在当前程序集范围内且永远不在其之外访问的方法,类等时。

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


6

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

您可以使用InternalsVisibleToAttribute声明一个朋友程序集,并且该朋友程序集中的所有引用都将在朋友程序集范围内将声明程序集的内部成员视为公共程序。一个问题是所有内部成员都是可见的。你不能选择。

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


5

作为经验法则,成员有两种:

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

4

降低噪音,暴露的类型越少,库就越简单。防篡改/安全性是另一种(尽管反射可以胜任)。


4

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

另外,如果程序集中存在错误,则修复程序引入重大更改的机会将减少。如果没有内部类,则必须假定更改任何类的公共成员将是一项重大更改。对于内部类,您可以假设修改其公共成员只会破坏程序集(以及InternalsVisibleTo属性中引用的任何程序集)的内部API。

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


2

我有一个使用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很多东西,并且证明它非常有用。


2

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

但是,在internal没有特定原因的情况下创建类或成员,就没有什么意义public,或者在private没有特定原因的情况下创建类或成员,我看不到任何意义。



1

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

如果您在工厂或其他中心位置用于构建对象,则程序集的用户仅需要处理公共接口或抽象基类。

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


1

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

欢迎对此方法发表评论。


@Samik-您能否详细说明一下,“通常建议您不要向程序集的外部用户公开List对象,而应公开IEnumerable。” 为什么选择无数?
Howiecamp 2010年

1
@Howiecamp:主要是因为IEnumerable提供了对该列表的只读访问权限,就像您直接公开该列表一样,它可以由客户端(例如delete元素等)进行修改,这可能是意外的。
Samik R

此外,公开List或IList会创建一个合同,您将始终允许(例如)快速随机访问数据。如果某天您决定更改方法以使用另一个集合,或者根本不使用任何集合(收益回报),则要么创建一个List仅仅返回一个值,要么通过更改方法的返回类型来打破合同。使用不太具体的返回类型可为您提供灵活性。

1

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

如果您Big_Important_Class的Project A项目打算在项目外部使用,则您不应对其进行标记internal

但是,在许多项目中,您经常会遇到实际上仅打算在项目内部使用的类。例如,您可能有一个类,用于保存参数化线程调用的参数。在这些情况下,您应该将它们标记为internal仿佛没有其他原因,除了保护自己免受将来意外的API更改之外。


那么为什么不使用private呢?
囚犯零

1
private关键字只能在被另一个类或结构内定义的类或结构一起使用。在命名空间中定义的类只能声明publicinternal
马特·戴维斯

1

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

  1. 在将来的版本中可能会发生变化(如果它们是公开的,则会破坏客户端代码)
  2. 对客户无用,可能会造成混乱
  3. 不安全(使用不当可能会严重破坏您的资料库)

等等

我想,如果您正在开发内部解决方案,那么使用内部元素并不那么重要,因为通常客户会与您保持联系并/或访问代码。但是,它们对于库开发人员而言至关重要。


-7

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

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

你想说什么 不太清楚。至少对我来说不是。
pqsk 2012年

同意@pqsk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.