連載:[完全版]究極のC#プログラミング

Chapter11 フレンドアセンブリ

川俣 晶
2010/01/18
Page1 Page2


 本記事は、(株)技術評論社が発行する書籍『[完全版]究極のC#プログラミング ― 新スタイルによる実践的コーディング』から、許可を得て転載しています。
 同書籍は、もともと本フォーラムにて連載していた『C# 2.0入門』、『C# 3.0入門』の記事を整理統合し、加筆、修正されたものです。

手元でまとめて読みたい方は、ぜひ書店などにてお買い求めください。

 【注意】本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

11.1 アセンブリ内部のメンバーへのアクセスを許す

 プログラミング言語の歴史をひもとくと、構造化プログラミングとオブジェクト指向プログラミングの中間に、「モジュール化プログラミング」という言葉が出てくることがある。たとえば、Modula-2が代表的なモジュール化プログラミング言語である。また、C言語はモジュール化プログラミングが可能な言語とされる。

 また、説明によっては、オブジェクト指向プログラミングがモジュール化プログラミングの進化形であるかのように扱われることもある。しかし、オブジェクト指向プログラミングはモジュール化プログラミングよりもすべてにおいて強力とは限らない。「スコープの制御」という観点で見ると、モジュール化プログラミングのほうが強力という解釈もありうるからだ。

 では、スコープ制御とは何だろうか? C#には、スコープを制御するためにpublic/protected/internal/protected internal/privateといったキーワードが存在する。ここで問題になるのは、publicの無制限さとinternalの厳しすぎる制限である。

 たとえば、アセンブリAがアセンブリBを利用し、アセンブリBがアセンブリCを利用しているプログラムがあったとしよう。アセンブリCは、別アセンブリであるアセンブリBからアクセス可能なクラスやメソッドを提供するためにpublic指定を使わなければならない。しかし、ひとたびpublicとして指定されたクラスやメソッドは、アセンブリB以外からもすべて自由にアクセスできてしまう。

 これに対して、モジュール化プログラミングでは、あるモジュールが参照するモジュールを明示的に宣言することでアクセス対象を宣言する。たとえば、モジュールAがモジュールBを利用し、モジュールBがモジュールCを利用しているとき、モジュールAは明示的にモジュールBの利用を宣言することで、モジュールBは見えてもモジュールCは見えない世界を作り出して動作させることができる。

 さて、この「publicの無制限さとinternalの厳しすぎる制限」という問題は、机上の空論ではない。現実問題として、C#開発の現場において、単体テストの作成時に顕著に発生するのである。

 通常、単体テストは正規の配布ファイルに含めないため、テスト対象のプログラムとは別個のアセンブリとして作成することが多い。ここで、単体テストがテスト対象のクラスやメソッドを呼び出すためには、テスト対象をpublicとする必要がある。しかし、それらは必ずしもpublicであるべきクラスやメソッドとは限らない。本当なら、内部でのみ使用されるものであり、外部からアクセス可能であってはならないものかもしれない。それにもかかわらず、テストの都合によって、外部からアクセス可能にせざるをえないのである。

 このような問題に対処するために、C# 3.0では「フレンドアセンブリ」という機能が追加されている。ちなみに、厳密にいえばこれはC# 3.0の機能ではなく、.NET Framework 2.0の新機能ということになる。実際、この機能はC# 3.0の言語仕様書には掲載されていない。しかし、C#プログラマーにとってはC# 3.0から利用可能になった新機能ということになるので、ここで紹介しよう。

■フレンドアセンブリの使い方

 フレンドアセンブリは、System.Runtime.CompilerServices名前空間のInternalsVisibleTo属性をアセンブリに付加することで実現する。

 この属性は、外部のアセンブリを明示的に指定することにより、アセンブリの内部に限定されたクラスやメソッドを、指定したアセンブリから参照可能にする(ただし、アセンブリ内部からであってもアクセスできないprivateなメソッドなどまでアクセス可能にするわけではない)。

 たとえば、表11.1のような3つのプロジェクトを含むソリューションがあったとする。

プロジェクト プロジェクトの内容
ConsoleApplication1 メインプログラム
ClassLibrary1 メインプログラムから利用されるクラスライブラリ
TestClassLibrary1 ClassLibrary1をテストする単体テストのクラスライブラリ
表11.1 3つのプロジェクトを含むソリューションの内容

 ClassLibrary1にはメインプログラムから直接呼ばれることはないがテストの対象としたいメソッドがあるとしよう。このようなメソッドは、TestClassLibrary1からは呼び出し可能であるが、ConsoleApplication1からは呼び出せないようにすべきである。この場合は、ClassLibrary1にInternalsVisibleTo属性を付加し、TestClassLibrary1からのアクセスだけは受け入れるように指定する。

 それに要する労力は、実質的にソースコードに属性の記述を1行挿入する手間だけである(厳密には、コンパイラに「/out」オプションを指定して明示的に出力ファイル名を指定する必要もあるのだが、Visual Studio 2005以降を普通に使う場合には自動的にそのオプションが指定されるので、特に意識して追加する必要はない)。

 では、上記のソリューションの例を見てみよう。このソリューションは、NUnit*6による単体テストを含んでいて、図11.1のような構成になっている。


図11.1ソリューションの構成
TestClassLibrary1はNUnitフレームワークによるテストメソッドを含んでおり、ClassLibrary1をテストするために存在する。

 各ソースコードの内容は、以下のリスト11.1リスト11.2リスト11.3のとおりである。

using System;
using ClassLibrary1;

class Program
{
  static void Main(string[] args)
  {
    PublicClass.Write(1, 2);
  }
}

リスト11.1 Program.cs

using System;
using System.Runtime.CompilerServices;

// TestClassLibrary1からのアクセスだけは受け入れる
[assembly: InternalsVisibleTo("TestClassLibrary1")]

namespace ClassLibrary1
{
  internal class InternalClass
  {
    internal static int Add(int x, int y)
    {
      return x + y;
    }
  }

  public class PublicClass
  {
    public static void Write(int x, int y)
    {
      Console.WriteLine(InternalClass.Add(x, y));
    }
  }
}
リスト11.2 Class1.cs

using System;
using NUnit.Framework;

namespace TestClassLibrary1
{
  [TestFixture]
  public class TestPeruAsinItem
  {
    // Class1.csのAddメソッドをテストするためのテストメソッド
    [Test]
    public void TestAdd()
    {
      Assert.AreEqual(3, ClassLibrary1.InternalClass.Add(1, 2));
    }
  }
}
リスト11.3 TestClass1.cs

 ここで、リスト11.2 Class1.csのPublicClassは、正規の利用方法として外部からのアクセスを無制限に受けることを意図したクラスである。それに対して、InternalClassはPublicClassを実装するための下請けクラスを意図したもので、外部から利用されるべきではないものとする(外部から利用されることがなければ、いつでも自由に書き直しができるから保守性が良くなる)。

 しかし、テスト駆動開発などを意図する場合は、外部から利用されない下請けクラスであっても、それに対する単体テストを作成しなければならない。そこで、アセンブリに対する属性を次のように記述し、明示的にTestClassLibrary1という名前のアセンブリを「フレンド」(内部へのアクセスを許す)と指定しているわけである。

// TestClassLibrary1からのアクセスだけは受け入れる
[assembly: InternalsVisibleTo("TestClassLibrary1")]

 これにより、アセンブリTestClassLibrary1に属するTestAddメソッドには、本来利用できないはずのInternalClassへのアクセスが許される。しかし、依然としてProgram.csからInternalClassへアクセスすることは許されない。フレンドアセンブリとしての指定が行われていないからである。

*6 本書ではテストフレームワークとしてNUnitを使用しているが、Visual Studioにビルドインされたテストフレームワークを使用する場合でもInternalsVisibleTo属性を使うのは同じである。本書の解説はどちらにも有効である。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter11 フレンドアセンブリ
  1.11.1 アセンブリ内部のメンバーへのアクセスを許す
    2.11.2 そもそも、アクセスを制限する価値とは?/練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間