連載:C# 2.0入門

第6回 部分クラスと静的クラス

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

静的クラス(Static Class)

 静的クラスは、デザインパターンでいうところの「シングルトン・パターン」を言語仕様レベルで記述するための新しい構文である。

 シングルトンとは、1つしか存在することができないオブジェクトを意味する。Javaで実装する場合は、要求されたときに必ず同じインスタンスを返す仕組みで実現するが、C#では静的なメンバだけで構成されるクラスを使って実現するのが一般的であるようだ。また、それが可能であるように、C#は静的なメンバを支援するための機能が強化されている。

 さらに、あるクラスのオブジェクトがシングルトンであることを貫徹するためには、そのクラスに「インスタンス化できない」という制約を付加する必要がある。これを実現するために、C# 1.xではprivateなコンストラクタを記述するという方法が提供されていた。

 しかし、これは便利とも分かりやすいともいえない。本来不要のコンストラクタを書かねばならない手間も無駄であるし、「インスタンス化させない」という意図を実現するために「インスタンス化されたときに実行されるコンストラクタを書く」というのも矛盾した対処である。

 そこで、C# 2.0ではより分かりやすい簡潔な構文で、インスタンス化できないクラスを記述できるようになった。クラス宣言の際に、staticキーワードを付けるだけである。

using System;

static class Sample // 静的クラス
{
}

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(new Sample()); // newはコンパイル・エラーになる
  }
}
リスト7 静的クラスの例

 このコードをコンパイルすると、インスタンス化(new)を行っている行で、

静的クラス 'Sample' のインスタンスを作成することはできません。

というエラーが起きて、インスタンスの作成が拒絶される。クラス宣言に付いたstaticキーワードを外すと、インスタンスは作成可能になる。

 さて、このような機能性を実現する短縮構文をわざわざ用意する意義がどれぐらいあるのだろうか?

 C# 1.x時代の感覚ではあまり使用頻度は高くなさそうに思えるかもしれない。しかし、C# 2.0では出番が増える可能性は考えられる。例えば、クラスは単なる入れ物として使われるケースが増えるという話はすでに行った。その際、入れ物は本当にシンプルな入れ物でよいことが多く、インスタンス化を必要としないことも多いのである。

 例えば、リスト5は同人ソフトのコードの一部の抜粋だが、まさにインスタンス化を必要としない入れ物の一例である(実はリリースされたクラスのソース・コードにはstaticを付け忘れているが、それはナイショである)。

 また、メソッドが主役となった結果として、ほかのメソッドから共通に利用されるメソッドを集めたクラスというものも典型的なパターンとしてよく出てくる。

 この場合、提供されるのは素のメソッドそのものなので、クラスそのものがインスタンス化されることはないし、する意味もない。リスト8は同じ同人ソフトから抜粋したコードの一例である(これも、リリースされたソース・コードにはstaticを付け忘れているが、それはナイショである)。

public static class General
{
  ……中略……

  // 場所間の移動メニューを表示する
  public static bool MoveMenu()
  {
    ……中略……
  }

  // ゲーム内で祭日として扱われる日であるかを判定する
  public static bool Is祭日(DateTime dateTime)
  {
    ……中略……
  }

  ……中略……
}
リスト8 メソッドの入れ物に徹したクラスの例

 もちろん、このようなクラスは昔から存在していた。「ユーティリティ・クラス」といった名前で呼ばれていたものにほかならない。それにもかかわらず、C# 2.0の時代になってそれを実現する専用の構文が与えられるようになったのは、これが「特殊なクラスの使い方」ではなく、「頻出する典型的な使い方」に変化したからだろう。

プロパティ・アクセサのアクセシビリティ

 C# 2.0では、プロパティのsetアクセサとgetアクセサに対して、異なるアクセシビリティを制限できる機能拡張が行われている。これにより、getアクセサはpublicだが、setアクセサはprotectedという非対称のプロパティを宣言できる。

 書き方は簡単で、getまたはsetキーワードの手前に、アクセシビリティを指定するキーワードを追加するだけである。それによって、プロパティ宣言そのものに付いているアクセシビリティ指定を上書きする。

using System;

class A
{
  private string s;

  public string SampleProp // 本来public指定だが……
  {
    get { return s; } // 本来のpublic指定が有効
    protected set { s = value; } // protectedで上書きしている
  }
}

class B : A
{
  public void Initialize()
  {
    SampleProp = "by class B";
  }
}

class Program
{
  static void Main(string[] args)
  {
    B b = new B();
    b.Initialize();
    Console.WriteLine(b.SampleProp); // 出力:by class B
  }
}
リスト9 getはpublicだがsetはprotectedの例

 さて、この機能は継承が絡むとややこしい問題を併発する場合がある。その事例を紹介しよう。

 次のリスト10は継承関係のある2つのクラスの双方が同じ名前のプロパティを宣言している例である。このコードでは、クラスBのSamplePropプロパティはprotectedなので、外部からはアクセスできない。だから、Mainメソッドが利用しているのはクラスAのSamplePropである。このように、アクセスできないプロパティをスキップし、アクセスできるプロパティが使われるようになっている。

using System;

class A
{
  private string s;

  public string SampleProp
  {
    get { return s; }
    set { s = value; }
  }
}

class B : A
{
  private string s;

  protected new string SampleProp // protectedなプロパティ
  {
    get { return s; }
    set { s = value; }
  }
}

class Program
{
  static void Main(string[] args)
  {
    B b = new B();
    b.SampleProp = "Sample";
    Console.WriteLine(b.SampleProp); // 出力:Sample
  }
}
リスト10 正常に動作するケース

 ここで、クラスBのプロパティをpublicにし、getアクセサとsetアクセサで異なるアクセシビリティを持たせてみよう。リスト10のクラスBをリスト11の内容に差し替えてみる。

class B : A
{
  private string s;

  public new string SampleProp
  {
    protected get { return s; }
    set { s = value; }
  }
}
リスト11 リスト10のクラスBを差し替えるとコンパイルできない

 すると、以下のようなエラーが起きてコンパイルできない。

get アクセサにアクセスできないため、プロパティまたはインデクサ 'B.SampleProp' はこのコンテキストでは使用できません。

 最初の例から考えると、protectedなSamplePropのgetアクセサを飛ばして、クラスAのSamplePropのgetアクセサを使ってくれそうに思えるかもしれないが、そのようには動作していない。get/setの手前のアクセシビリティ指定は、メンバの検索やオーバーロードの解決には影響しないからである。つまり、getアクセサのprotectedに関係なく、クラスBのpublicなSamplePropプロパティは発見されてしまい、それにprotectedが付いているためにアクセスできないというエラーになるわけである。

アクセシビリティ指定の制約

 このようなアクセシビリティ指定は常に可能というわけではない。まず、指定できるアクセシビリティはより制限を強くする方向にしか指定できない。publicと宣言したプロパティのget/setアクセサにprotectedを指定することはできるが、逆はできない。

 また、インターフェイス・メンバの実装時には、そもそもこのようなアクセシビリティ指定を付けることができない。リスト12は、それを付けることでコンパイル・エラーになる例である。

using System;

public interface A
{
  string SampleProp
  {
    get;
  }
}

public class B : A
{
  private string s;

  public string SampleProp
  {
    internal get { return s; } // コンパイル・エラーになる
    set { s = value; }
  }
}

class Program
{
  static void Main(string[] args)
  {
    B b = new B();
    b.SampleProp = "Sample";
    Console.WriteLine(((A)b).SampleProp);
  }
}
リスト12 インターフェイス・メンバの実装時にアクセシビリティを指定する

 リスト12をコンパイルすると以下のようなエラーになる。

'B' はインターフェイス メンバ 'A.SampleProp.get' を実装しません。'B.SampleProp.get' は、パブリックではありません。

 これは、インターフェイス・メンバのアクセシビリティはプログラマーが指定できない性質のものであり、ほかの場所で上書きできないことを意味している。

次回予告

 次回は、名前空間のエイリアス修飾子と外部アセンブリについて取り上げる。名前空間のエイリアス修飾子、外部アセンブリのエイリアス、フレンド・アセンブリ、フレンド・アセンブリを使った柔軟なスコープ制限開発などを解説する予定である。

 この連載もあと2回で完結する。C# 2.0の制覇まであと一息である。頑張ろう!


 INDEX
  C# 2.0入門
  第6回 部分クラスと静的クラス
    1.値型と参照型の相違は何か?
    2.部分クラス/自動生成コードと安全に共存する
    3.リフレクションと部分クラス/部分クラスの注意点
  4.静的クラス/アクセサのアクセシビリティ/アクセシビリティ指定の制約
 
インデックス・ページヘ  「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 記事ランキング

本日 月間