M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

空のAssemblyKeyFile・AssemblyKeyName属性の罠

最近仕事でひとさまがC#で作成したライブラリをリファクタリングする機会がありました。既存コードに対してもそうですが、とくにリファクタリングで追加したコードに対してはきっちりUTを実施する方針です。

そこで既存のプロジェクトに加えて同一ソリューション内にNUnitを使用したテスト用プロジェクトを加えました:

─Sample.Solution
  ├Sample.Solution.sln
  ├...
  ├Sample.Project
  │  ├Sample.Project.csproj
  │  ├...
  │  └Properties
  │    └AssemblyInfo.cs
  └Test.Sample.Project
      ├Test.Sample.Project.csproj
      ├...
      └...

そして既存プロジェクトのAssembyInfo.csに、アセンブリに対するInternalsVisibleTo属性のコードを追加しました:

[assembly: InternalsVisibleTo("Test.Sample.Project")]

APIの公開メンバーでなくとも、UTを実施したいということは当然ありえます。私見ですが、公開メンバーが限定されており、内部実装がきっちり隠蔽されたAPIほど、そうした需要は大きくなる傾向すらあるのではないかと考えています。

このようなときテスト対象のアセンブリに対して上記の属性指定を行い、テストコードが含まれるアセンブリ名を指定しておくと、テスト対象アセンブリinternalアクセス修飾子が指定されたメンバーがテストコードアセンブリから見てpublicなものという扱いになります。これを「フレンドアセンブリ」といいます。

このソリューションはVisual Studio 2017では正常にビルドすることができました。ところが同じソリューションをVisual Studio 2013でオープンしてビルドしようとすると次のエラーが発生してしまいます:

エラー 47 フレンド アセンブリ参照 'Test.Sample.Project' は無効です。厳密な名前の署名つきアセンブリはその InternalsVisibleTo 宣言内で公開キーを指定しなければなりません。 X:\path\to\AssemblyInfo.cs 60 31 Sample.Project

たしかに厳密な名前の署名付きアセンブリであれば、InternalsVisibleTo属性によってメンバーを公開するのに、しかるべき手間暇が必要になります。

しかし今回作業対象としていたプロジェクトはいずれも厳密な名前の署名付きアセンブリではありません。それはVisual Studioのプロジェクトのプロパティ画面を見ても明らかでした。

あれこれ調べて見た後でわかったのはInternalsVisibleTo属性を追加する以前からAssemblyInfo.csに存在していた次の2つの属性でした:

[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]

この属性たちを削除するとコンパイルは成功するようになりました。

この属性があるとVisual Studio 2013が内蔵するコンパイラをして、「このプロジェクトの成果物であるアセンブリは厳密な名前の署名付きアセンブリだから、メンバ公開対象のアセンブリ名だけをパラメータとしてとるInternalsVisibleTo属性は無効である」と判断させてしまうらしいのです。

つまりAssemblyKeyFile属性やAssemblyKeyName属性の実際の値を見てそれが厳密な名前の署名付きアセンブリであるかどうかを判断する前に、InternalsVisibleTo属性は無効と判断してしまうらしいのです。

Visual Studio 2013でも2017でも、プロジェクトを作成したときこれらの属性がAssemblyInfo.csに自動でコードされるなどということはありませんでした。おそらくリファクタリング対象のこのプロジェクトを過去に作成した開発者が、一度厳密な名前の署名付きアセンブリを作成しようとして結局やめたのでしょう。あとに残されたのは空文字列が指定された2つの属性だったわけです。

その後、例によってGoogle先生に質問してみると、ほんの僅かですがこの問題について触れたWebページが見つかりました。例えばこちらの2008年9月2日付の記事".NET 2.0 InternalsVisibleTo Attribute and Unsigned Assemblies"の記述:

It turns out that the compiler considers your assembly as "signed" if there is either an AssemblyKeyFile or AssemblyKeyName defined on the assembly, even though both of them are empty.
So, to be able to use AssemblyKeyName InternalsVisibleTo with unsigned assemblies, just remove AssemblyKeyFile or AssemblyKeyName attributes if you don't use them.

(AssemblyKeyFile属性もしくはAssemblyKeyName属性が当該アセンブリ上に定義されているとき、たとえその値が空文字列であっても、コンパイラはそのアセンブリを「署名付き」とみなしてしまう。そういうわけで、署名付きでないアセンブリでInternalsVisibleTo属性を使用する場合、AssemblyKeyFile属性とAssemblyKeyName属性を削除してやる必要がある。)

どうやらこの記事が執筆された2008年ころ(=Visual Studio 2008?)から、Visual Studio 2013のころまで、Visual Studio 2013に内臓されたC#コンパイラはそのような動作をしていたようです。そして少なくとも直近Visual Studio 2017の時点ではこの動作は変化しており、AssemblyKeyFile属性とAssemblyKeyName属性が空文字列とともに使用されている場合、当該アセンブリを厳密な名前の署名付きアセンブリとはみなさないようになっていたようです。

思いつく限りいろいろなキーワードでGoogle検索してみましたがこれ以上の情報は見つかりませんでした。