++C++; // 未確認飛行 C

Google
Web ufcpp.net

プロパティ

目次

キーワード

概要

プロパティ(property:所有物、特性)とは、JavaやC++にはない(Visual Basicにはある)機能で、 クラス外部から見るとメンバ変数のように振る舞い、 クラス内部から見るとメソッドのように振舞うものです。

JavaやC++がこの機能を持ってないことからも分かると思いますが、 プロパティはオブジェクト志向言語に必須の機能ではありません。 しかし、これから説明していくように、あると便利なものです。

プロパティとは

実装の隠蔽」 で、 メンバ変数はクラス外部から直接アクセス出来ないようにして、 オブジェクトの状態の変更はすべてメソッドを通して行うべきだと書きました。 これを忠実に実行すると、クラスを利用する側のコードは以下の例のように少々見栄えの悪いものになってしまいます。

using System;

// 「実装の隠蔽」で作った複素数クラス
class Complex
{
  // 実装は外部から隠蔽(privateにしておく)
  private double re; // 実部を記憶しておく
  private double im; // 虚部を記憶しておく

  public double Re(){return this.re;}    // 実部を取り出す
  public void Re(double x){this.re = x;} // 実部を書き換え

  public double Im(){return this.im;}    // 虚部を取り出す
  public void Im(double y){this.im = y;} // 虚部を書き換え

  public double Abs(){return Math.Sqrt(re*re + im*im);}  // 絶対値を取り出す
}

// クラス利用側
class ConcealSample
{
  public static void Main()
  {
    // x = 5 + 1i
    Complex x = new Complex();
    x.Re(5);  // x.re = 5
    x.Im(1);  // x.im = 1

    // y = -2 + 3i
    Complex y = new Complex();
    y.Re(-2); // y.re = -2
    y.Im( 3); // y.im =  3

    Complex z = new Complex();
    z.Re(x.Re() + y.Re()); // z.re = x.re + y.re
    z.Im(x.Im() + y.Im()); // z.im = x.im + y.im

    Console.Write("|{0} + {1}i| = {2}\n", z.Re(), z.Im(), z.Abs());
    // |3 + 4i| = 5 と表示される
  }
}

void Re(double x)double Re()などの、 メンバ変数の値の取得・変更を行うためのメソッドのことをアクセサ(accessor)といいます。 C++やJavaなどの言語では、下手をするとメンバ変数の数だけアクセサが存在するという状態になることもあります。 C++やJavaではアクセサのメソッド名はvoid SetRe(double x)double GetRe()というように、メンバ変数名に Set/Get をつけた物を使うことが多く、メンバ変数の数だけ Set/Get で始まるメソッドのペアができ、ちょっと見苦しいものになります。 (参考: 「Set / Get とプロパティ」 )

また、クラス作成側からすると、オブジェクトの状態の取得・変更はすべてメソッドを通して行ったほうがいいのですが、 クラス利用側からすると、メンバ変数に値を直接代入するほうが見た目がすっきりします。

このような理由から、 C#では クラス内部から見るとメソッドのように振る舞い、 クラス利用側から見るとメンバ変数のように振舞う プロパティという機能を用意しました。 プロパティの定義の仕方は以下のような書式になります。

アクセスレベル 型名 プロパティ名
{
  set
  {
    // setアクセサ
    //  ここに値の変更時の処理を書く。
    //  value という名前の変数に代入された値が格納される。
  }
  get
  {
    // getアクセサ
    //  ここに値の取得時の処理を書く。
    //  メソッドの場合と同様に、値はreturnキーワードを用いて返す。
  }
}

例えば先ほどの複素数クラスのアクセサをプロパティを使って書き換えると以下のようになります。

using System;

// クラス定義
class Complex
{
  // 実装は外部から隠蔽(privateにしておく)
  private double re; // 実部を記憶しておく
  private double im; // 虚部を記憶しておく

  // 実部の取得・変更用のプロパティ
  public double Re
  {
    set{this.re = value;}
    get{return this.re;}
  }
  /* ↑のコードは意味的には以下のコードと同じ。
  public void SetRe(double value){this.re = value;}
  public double GetRe(){return this.re;}
  メソッドと同じ感覚で使える。
  */

  // 実部の取得・変更用のプロパティ
  public double Im
  {
    set{this.im = value;}
    get{return this.im;}
  }

  // 絶対値の取得用のプロパティ
  public double Abs
  {
    // 読み取り専用プロパティ。
    // setブロックを書かない。
    get{return Math.Sqrt(re*re + im*im);}
  }
}

// クラス利用側
class PropertySample
{
  public static void Main()
  {
    Complex c = new Complex();
    c.Re = 4; // Reプロパティのsetアクセサが呼び出される。
    c.Im = 3; // Imプロパティのsetアクセサが呼び出される。
    Console.Write("|{0} + ", c.Re); // Reプロパティのgetアクセサが呼び出される。
    Console.Write("{0}i| =", c.Im); // Imプロパティのgetアクセサが呼び出される。
    Console.Write(" {0}\n", c.Abs); // Absプロパティのgetアクセサが呼び出される。
  }
}

実装の隠蔽」 のときと同様に、 このコードの実装方法を 「実部と虚部をメンバ変数に記憶しておく」方法から 「絶対値と偏角をメンバ変数に記憶しておく」方法に変更しても、 以下のように、クラス利用側のコードに手を加える必要は一切ありません。

using System;

// クラス定義
class Complex
{
  // 実装は外部から隠蔽(privateにしておく)
  private double abs; // 絶対値を記憶しておく
  private double arg; // 偏角を記憶しておく

  // 実部の取得・変更用のプロパティ
  public double Re
  {
    set
    {
      double im = this.abs * Math.Sin(this.arg);
      this.abs = Math.Sqrt(value*value + im*im);
      this.arg = Math.Atan2(im, value);
    }
    get
    {
      return this.abs * Math.Cos(this.arg);
    }
  }

  // 実部の取得・変更用のプロパティ
  public double Im
  {
    set
    {
      double re = this.abs * Math.Cos(this.arg);
      this.abs = Math.Sqrt(value*value + re*re);
      this.arg = Math.Atan2(value, re);
    }
    get
    {
      return this.abs * Math.Sin(this.arg);
    }
  }

  // 絶対値の取得用のプロパティ
  public double Abs
  {
    get{return this.abs;}
  }
}

// クラス利用側
class PropertySample
{
  public static void Main()
  {
    Complex c = new Complex();
    c.Re = 4; // クラス利用側は一切変更せず
    c.Im = 3;
    Console.Write("|{0} + ", c.Re);
    Console.Write("{0}i| =", c.Im);
    Console.Write(" {0}\n", c.Abs);
  }
}

set/get で異なるアクセスレベルを設定

Ver. 2.0

C# 2.0 の新機能で、 プロパティの set/get アクセサそれぞれ異なるアクセスレベルを設定できるようになりました。

class A
{
  private int n;

  public int N
  {
    get{ return this.n; }
    protected set{ this.n = value; }
  }
}

自動プロパティ

Ver. 3.0

C# 3.0 では、プロパティの get/set の中身の省略もできるようになりました。 この機能を自動プロパティ(auto-property, auto-implemented property)といいます。

例えば、

public string Name { get; set; }

というように、 get; set; とだけ書いておくと、

private string __name;
public string Name
{
  get { return this.__name; }
  set { this.__name = value; }
}

というようなコードに相当するものが自動的に生成されます。 (__name という変数名はプログラマが参照できるものではありません。)

C# プログラミングでは、 この手のコード(メンバ変数 name をプロパティ Name で覆う)は定型文的によく使います。 また、クラス内からであっても、private のメンバ変数には直接アクセスせず、 プロパティを通してアクセスする方が後々の保守がしやすかったりします。 ということで、自動プロパティのような省略記法が導入されました。

ちなみに、元々 C# 2.0 以前でも、 「プロパティのデリゲート版」にあたるイベントでは自動プロパティを同じような省略が可能でした。 (デリゲート、イベントについては後述。 参考: 「デリゲート」 、 「イベント」 。) その省略機能がプロパティにも実装されたということになります。

演習問題

問題 1

クラス問題 1Point 構造体および Triangle クラスの各メンバ変数に対して、 プロパティを使って実装の隠蔽を行え。

解答

Transtation into English

[お問い合わせ](q)