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

时间:2020-03-06 15:03:42  来源:igfitidea点击:

我们能否解释一下,C#中的" internal"关键字有什么实际用途?

我知道internal修饰符会限制对当前程序集的访问,但是我什么时候需要它?

解决方案

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

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

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

从MSDN:

A common use of internal access is in component-based development because it enables a group of components to cooperate in a private manner without being exposed to the rest of the application code. For example, a framework for building graphical user interfaces could provide Control and Form classes that cooperate using members with internal access. Since these members are internal, they are not exposed to code that is using the framework.

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

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

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

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

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

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

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

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

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

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

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

如果我们在工厂或者某个其他中心位置用于构造对象,则装配件的用户只需处理公共接口或者抽象基类。

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

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

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

根据经验法则,成员有两种:

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

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

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

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

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

我有一个使用LINQ-to-SQL作为数据后端的项目。我有两个主要的命名空间:Biz和Data。 LINQ数据模型位于Data中,并标记为"内部"。 Biz名称空间具有公共类,这些公共类包装了LINQ数据类。

因此,有" Data.Client"和" Biz.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)无法直接访问数据模型,从而确保业务层始终充当中介。

这是我第一次真正使用"内部",并且事实证明它非常有用。

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

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

如果我们正在编写将大量复杂功能封装到一个简单的公共API中的DLL,则在不会公开公开的类成员上使用internal。

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

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

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

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

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

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

欢迎对此方法发表评论。

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

但是,我认为在没有特定原因的情况下将类或者成员设置为"内部"是没有意义的,而在没有特定原因的情况下将其设置为"公开"或者"私有"是没有意义的。

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

如果项目A的" Big_Important_Class"打算在项目外部使用,则不应在"内部"标记。

但是,在许多项目中,我们通常会拥有实际上仅打算在项目内部使用的类。例如,我们可能有一个类,用于保存参数化线程调用的参数。在这些情况下,如果我们仅出于保护自己不受意外API更改的其他原因,则应将它们标记为"内部"。

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

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

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

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

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

等等。

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

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

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

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

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