ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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


r
ruffin

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

来自 MSDN(通过 archive.org):

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

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

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


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

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

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

内部的意义不是它让鲍勃生活困难。正是因为它允许您控制项目 A 对特性、生命周期、兼容性等做出的昂贵承诺。


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

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


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

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

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


E
Edwin Jarvis

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

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

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


I
Ilya Komakhin

在“尽可能使用严格的修饰符”规则的驱动下,我在需要访问另一个类的方法(例如,在我明确需要从另一个程序集访问它之前)的任何地方都使用内部。

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


如果您“尽可能使用严格的修饰符”,那为什么不私有呢?
由于当必须在类外部访问类属性/方法时,private 过于严格,而必须从另一个 asse 访问它们的情况并不常见。
m
mattlant

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

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

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

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

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


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

前几天,也许是一周,在一个我不记得的博客上看到了一个有趣的东西。基本上我不能对此表示赞赏,但我认为它可能有一些有用的应用程序。

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

namespace Base.Assembly
{
  public abstract class Parent
  {
    internal abstract void SomeMethod();
  }

  //This works just fine since it's in the same assembly.
  public class ChildWithin : Parent
  {
    internal override void SomeMethod()
    {
    }
  }
}

namespace Another.Assembly
{
  //Kaboom, because you can't override an internal method
  public class ChildOutside : Parent
  {
  }

  public class Test 
  { 

    //Just fine
    private Parent _parent;

    public Test()
    {
      //Still fine
      _parent = new ChildWithin();
    }
  }
}

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


P
Prisoner ZERO

此示例包含两个文件: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 的可访问性级别更改为内部。在这种情况下,您可以实例化类,但不能访问内部成员。

// 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


A
Aaron Powell

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

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


c
cfeduke

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

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

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


R
Rod

根据经验,有两种成员:

公共表面:从外部程序集中可见(公共、受保护和内部受保护):调用者不受信任,因此需要参数验证、方法文档等。

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


Q
Quibblesome

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


x
xofz

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

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

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


A
Andrew Kennan

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

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

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


K
Keith Williams

我有一个项目使用 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,它被证明非常有用。


F
Fredrik Mörk

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

但是,我看不出没有特定原因将类或成员设为 internal 有任何意义,就像在没有特定原因的情况下将它们设为 publicprivate 一样有意义。


S
Steven A. Lowe

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


S
Samik R

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

欢迎对这种方法发表评论。


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

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

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

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


那么为什么不使用 private 呢?
private 关键字只能用于在另一个类或结构中定义的类或结构。在命名空间中定义的类只能声明为 publicinternal
G
Grzenio

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

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

等等

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


y
yfeldblum

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

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

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