連載:C# 2.0入門

第7回 名前空間のエイリアス修飾子と外部アセンブリ

株式会社ピーデー 川俣 晶
2007/11/30
Page1 Page2 Page3

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

 プログラミング言語の歴史をひもとくと、構造化プログラミングとオブジェクト指向プログラミングの中間に、モジュール化プログラミングという言葉が出てくることがある。例えば、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# 2.0ではフレンド・アセンブリという機能が追加されている。ちなみに、厳密にいえばこれはC# 2.0の機能ではなく、.NET Framework 2.0の新機能ということになる。実際、この機能はC# 2.0の言語仕様書には掲載されていない。しかし、C#プログラマーにとってはC# 2.0から利用可能になった新機能ということになるので、ここで紹介しよう。

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

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

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

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

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

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

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

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


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

using System;
using ClassLibrary1;

class Program
{
  static void Main(string[] args)
  {
    PublicClass.Write(1, 2);
  }
}
リスト8 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));
    }
  }
}
リスト9 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));
    }
  }
}
リスト10 TestClass1.cs

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

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

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

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

そもそもアクセスを制限する価値とは?

 このようなアクセス制限の価値がいまひとつ分からない読者も多いだろう。

 どうせ呼び出さないクラスやメソッドがいくら増えても、呼び出さなければ影響はないというのも1つの考えだろう。もちろん、コードを書き換えたときに名前の衝突という問題が起きやすくなるが、それは深刻な問題ではない……と考えることもできるだろう。名前など、書き換えてしまえばどうとでもなるのである。

 しかし、これらはいざ問題を起こすと深刻で奥深いトラブルを起こすことがあり、避けられるものなら避けた方が保守性や生産性が高くなるものである。

 だが、それ以前にもっと明確なメリットがある。

 Visual StudioのIntelliSence(インテリセンス)に大量に出てくる候補を減らし、目的の名前を探しやすくできるのだ。例えば上記の例でいえば、Program.csでIntelliSenceを機能させてもInternalClassという名前は出ない。もし、フレンド・アセンブリを使わず、public指定を使っていたなら、IntelliSenceの候補にInternalClassが出てしまうのである。

 もちろん1つぐらいならどうということはないが、実用プログラムで数十、数百のクラスが絡み合っているとき、IntelliSenceに出てくる名前を大幅に減らせることは大いなるメリットといえるのではないだろうか?

次回予告

 次回はいよいよ最終回である。落ち穂拾い的に書き残した細かい機能について説明していく。

 以下のような話題が主な予定である。

  • 固定サイズ・バッファ
  • IntPtr型および UIntPtr型へのvolatile
  • インライン警告制御/#pragma 警告
  • C#コンパイラの新機能

 すでにC# 2.0の時代は終わりを告げつつあるように思えるかもしれないが、C# 3.0時代の到来が間近ということは、裏を返せばC# 2.0の円熟期を意味する。使っていますぐおいしいC# 2.0をぜひマスターしよう。End of Article


 INDEX
  C# 2.0入門
  第7回 名前空間のエイリアス修飾子と外部アセンブリ
    1.未来志向/バージョンによるコード破壊/グローバルな名前空間の強制
    2.アセンブリ間の名前競合の解決
  3.アセンブリ内部へのアクセス/フレンド・アセンブリ/アクセスを制限する価値
 
インデックス・ページヘ  「C# 2.0入門」


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 記事ランキング

本日 月間