.NET 中的 InternalsVisibleTo

Intro

在 .NET 项目中一些不想要暴露出去的类型一般会声明为 internal 这样就可以做到只有该程序集中的代码可以直接访问这个类型了,但有时候我们要写一些测试代码的时候可能会需要在测试项目中使用到项目中的 internal 类型或者复用某一段逻辑,如果直接使用则会遇到类似 Cannot access internal class 'InternalHelper' here 这样的错误提示.

.NET Framework 2.0 开始引入了 InternalsVisibleToAttribute 来支持访问项目中的 Internal 类型,在 .NET(Core) 有了新的使用方式,我们一起来看一下吧

InternalsVisibleToAttribute

InternalsVisibleToAttribute 是一个作用于 Assembly 的一个 Attribute,定义如下:

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class InternalsVisibleToAttribute : Attribute
{
    public InternalsVisibleToAttribute(string assemblyName)
    {
        AssemblyName = assemblyName;
    }

    public string AssemblyName { get; }
    public bool AllInternalsVisible { get; set; } = true;
}

https://github.com/dotnet/runtime/blob/3fc61ebb562afc327a8fc6de5c82d76e86bf6f5d/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/InternalsVisibleToAttribute.cs#L7

使用起来就和其他的 attribute 一样,使用示例如下:

    [assembly:InternalsVisibleTo("UnitTest")]
// InternalsVisibleTo with public key
[assembly:InternalsVisibleTo("UnitTest, PublicKey=xxxxx")]

参数是 Assembly 的名字,如果程序集是强签名的需要指定 PublicKey,语法是 AssemblyName, PublicKey=<xxxx>

另外 assembly attribute 的使用一般需要声明在命名空间之前,别的命名空间 using 之后

在 .NET Framework 通常会将这样的信息声明在 AssemblyInfo,在 .NET Core 默认模板没有了 AssemblyInfo 文件,通常建议新建一个类似于 AssemblyInfo 的文件来统一定义 assembly 相关的信息,assembly attribute 的使用

MSBuild Support

除了在代码里声明 InternalsVisibleTo 之外,在 .NET Core 项目里我们还可以使用在项目文件中进行定义,MS Build 会在编译时生成 assembly attribute 信息,和自己直接在代码里声明 attribute 的效果是一样的

AssemblyAttribute

在 .NET(Core) 项目中我们可以在项目文件中定义 AssemblyAttribute 的 item,用于生成 assembly attribute,使用示例如下:

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
    <_Parameter1>UnitTest</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

如果需要指定 Public Key 则在声明参数的时候指定即可,和在代码里使用一致

<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
    <_Parameter1>UnitTest, PublicKey=xxxxx</_Parameter1>
</AssemblyAttribute>

AssemblyAttribute 除了使用 InternalsVisibleToAttribute 也可以用于其他的 Attribute 的使用,如果没有参数则不需要声明 _Parameter,多个参数就声明多个 _Parameter,如:_Parameter2 ...

InternalsVisibleTo

从 .NET 5 开始,支持了一种更为简化的方式来声明,可以直接在项目文件中使用 InternalsVisibleTo ,示例如下:

<ItemGroup>
    <InternalsVisibleTo Include="UnitTest" />
</ItemGroup>

如果需要指定 PublicKey,目前有两种方式一种是类似于 AssemblyAttribute 的使用,另外一种则是可以单独使用 Key 来指定程序集的 PublicKey,示例如下:

<InternalsVisibleTo Include="UnitTest, PublicKey=xxxxx" />
<InternalsVisibleTo Include="UnitTest" Key="xxxxx" />
<InternalsVisibleTo Include="UnitTest">
  <Key>xxxxx</Key>
</InternalsVisibleTo>

这几种方式都是等价的

目前指定 PublicKey 使用的 Key 而不是 PublicKey,这对于很多开发者来说,造成了一定的困扰,很多人觉得用 PublicKey 更合理一些,前段时间使用添加了 PublicKey 的支持,原来的 Key 还是支持的,应该在 .NET 7 会变得可用,喜欢使用 PublicKey 就会变成下面这样:

<InternalsVisibleTo Include="UnitTest" PublicKey="xxxxx" />

感兴趣的可以参考:

https://github.com/dotnet/msbuild/issues/7336

https://github.com/dotnet/sdk/issues/13289

https://github.com/dotnet/sdk/pull/25000

More

除了测试项目外,关系比较紧密的项目也可以使用这个功能来复用一些代码。

在项目文件中声明可以利用一些 MSBuild 的内置变量或特性,使得配置更加灵活,也可以结合 Directory.Build 来消除一些重复性的配置,所以更加推荐在项目文件中使用 InternalsVisibleTo

<InternalsVisibleTo Include="$(AssemblyName).UnitTest" />
<InternalsVisibleTo Include="$(AssemblyName).IntegrationTest" />

如果一定要在代码里定义,强烈推荐把所有的 assembly 相关的定义信息放在一起,避免在多个地方重复定义相同的 attribute