“内部”的实际应用关键字

你能解释一下c#中internal关键字的实际用法吗?

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

234076 次浏览

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

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

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

来自MSDN (via archive.org):

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

你也可以使用内部修饰符和InternalsVisibleTo程序集级别属性来创建“友程序集”,它们被授予对目标程序集内部类的特殊访问权。

这对于创建单元测试程序集非常有用,然后允许这些程序集调用要测试的程序集的内部成员。当然,没有其他程序集被授予这种级别的访问权限,所以当您发布系统时,将维护封装。

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

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

我发现内部被过度使用了。您真的不应该只向某些类公开某些功能,而不向其他使用者公开。

在我看来,这打破了界面,打破了抽象。这并不是说永远不应该使用它,而是更好的解决方案是重构到不同的类,或者在可能的情况下以不同的方式使用。然而,这并不总是可能的。

它可能导致问题的原因是,另一个开发人员可能负责在与您的程序集中构建另一个类。内部元素会降低抽象的清晰度,如果被误用,可能会导致问题。如果你把它公诸于众,那也是一样的问题。由其他开发人员构建的另一个类仍然是消费者,就像任何外部类一样。类抽象和封装不仅仅是为了保护外部类,而是为了保护所有类。

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

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

唯一一件事我曾经使用内部关键字是许可证检查代码在我的产品;-)

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

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

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

在“使用尽可能严格的修饰符”规则的驱动下,我在需要访问的任何地方都使用internal,比如,从另一个类中访问方法,直到我显式地需要从另一个程序集访问它。

由于程序集接口通常比类接口的总和更窄,所以我在很多地方使用它。

根据经验,有两种成员:

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

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

internal的一个非常有趣的用法(internal member当然仅限于声明它的程序集)是在某种程度上从它获得“好友”功能。友成员是仅对声明它的程序集之外的某些其他程序集可见的对象。c#没有内置对friend的支持,但是CLR有。

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

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

我有一个项目,使用LINQ-to-SQL的数据后端。我有两个主要的名称空间:Biz和Data。LINQ数据模型存在于数据中,并标记为“内部”;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,而且它被证明非常有用。

前几天,也许是一个星期,在一个我不记得的博客上看到了一个有趣的东西。基本上这不是我的功劳,但我认为它可能有一些有用的应用。

假设您想让另一个程序集看到一个抽象类,但不希望有人能够继承它。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();
}
}
}

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

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

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

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

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

当你有一个基于C/ c++的库,你想要DllImport,你可以导入这些函数作为一个类的静态函数,并使他们内部,所以你的用户只能访问你的包装器,而不是原始的API,所以它不能乱动任何东西。函数是静态的,您可以在程序集中的任何地方使用它们,用于您需要的多个包装器类。

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

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

欢迎对这种方法提出意见。

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

然而,如果没有特定的原因,我看不出将类或成员设为internal有任何意义,就像没有特定的原因将它们设为publicprivate一样没有意义。

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

如果项目A的Big_Important_Class打算在项目外部使用,则不应该将其标记为internal

然而,在许多项目中,经常会有一些实际上只打算在项目中使用的类。例如,您可能拥有一个类,该类保存参数化线程调用的参数。在这些情况下,你应该将它们标记为internal,如果没有其他原因,只是为了保护自己免受意外的API更改。

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

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

内在的意义并不在于它让鲍勃的生活变得困难。而是它允许您控制Project A在特性、生命周期、兼容性等方面做出的昂贵承诺。

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

  1. 可能在未来的版本中更改(如果它们是公开的,您将破坏客户端代码)
  2. 对客户是无用的,可能会造成混乱
  3. 不安全(所以使用不当会严重破坏你的库)

等。

如果你在开发内部解决方案,那么使用内部元素就不是那么重要了,因为通常客户会经常与你联系,并且/或访问代码。不过,它们对库开发人员来说相当重要。

内部类使您能够限制程序集的API。这有很多好处,比如让你的API更容易理解。

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

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

这个例子包含两个文件: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的可访问性级别更改为公共。还要将成员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