C Sharpの基礎 - プロパティ

提供:MochiuWiki : SUSE, EC, PCB
2021年11月18日 (木) 10:21時点におけるWiki (トーク | 投稿記録)による版 (文字列「<source lang」を「<syntaxhighlight lang」に置換)
ナビゲーションに移動 検索に移動

概要

プロパティとは、 クラスの外部から見るとメンバ変数のように振る舞い、 クラス内部から見るとメソッドのように振舞うものである。
実装の隠蔽(カプセル化)の原則を崩すことなく、 アクセサ関数の煩雑さを解消するためのものである。


アクセサ関数とは

アクセサ関数とは、以下の例のように、void Re(double x)やdouble Re()等のメンバ変数の値の取得・変更を行うためのメソッドのことである。
C++やJavaでは、アクセサ関数のメソッド名はvoid SetRe(double x)やdouble GetRe()というように、メンバ変数の数だけアクセサ関数が必要になってしまう。

 using System;
 
 //  複素数クラス
 class Complex
 {
    // 実装は外部から隠蔽する
    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 Sample
 {
    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 と表示される
    }
 }
 </source>
<br><br>

== プロパティ ==
アクセサ関数の煩雑さを減らすために、クラス内部から見るとメソッドのように振る舞い、 クラス利用側から見るとメンバ変数のように振舞うプロパティ機能がある。<br>
<br>
プロパティの定義の仕方は以下のようなシンタックスになる。<br>
set移行のブロックに値の変更用の処理を、get移行のに値の取得用の処理を記述する。<br>
 <syntaxhighlight lang="c#">
 アクセスレベル 型名 プロパティ名
 {
    set
    {
       //  setアクセサ(setterとも言う)
       //  ここに値の変更時の処理を記述する
       //  valueという名前の変数に代入された値が格納される
    }
    get
    {
       //  getアクセサ(getterとも言う)
       //  ここに値の取得時の処理を記述する
       //  メソッドの場合と同様に、値はreturnキーワードを用いて返す
    }
 }
 </source>
<br>
例えば、上記の複素数クラスのアクセサをプロパティを使用して書き換えると以下のようになる。<br>
 <syntaxhighlight lang="c#">
 using System;
 
 // 複素数クラス
 class Complex
 {
    // 実装は外部から隠蔽(privateにしておく)
    private double re; // 実部を記憶しておく
    private double im; // 虚部を記憶しておく
 
    // 実部の取得・変更用のプロパティ
    public double Re
    {
       set { this.re = value; }
       get { 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 Sample
 {
    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アクセサが呼び出される
    }
 }
 </source>
<br><br>

== set / getで異なるアクセスレベルを設定 ==
===== set / getで異なるアクセスレベルを設定 =====
C# 2.0から、プロパティのset/getアクセサそれぞれ異なるアクセスレベルを設定できる。
 <syntaxhighlight lang="c#">
 class A
 {
    private int n;
 
    public int N
    {
       get{ return this.n; }
       protected set{ this.n = value; }
    }
 }
 </source>
<br>
===== 自動プロパティ =====
C# 3.0から、プロパティのget/setの中身の省略ができる。この機能を自動プロパティと呼ぶ。<br>
また、コンパイラによって生成されるフィルド(上記の例では、__name)をバックフィルドと呼ぶ。<br>
 <syntaxhighlight lang="c#">
 public string Name { get; set; }
 </source>
<br>
上記の自動プロパティは、コンパイラは下記のように解釈する。<br>
 <syntaxhighlight lang="c#">
 private string __name;
 public string Name
 {
    get { return this.__name; }
    set { this.__name = value; }
 }
 </source>
<br>
自動プロパティを使用した複素数の例を記述する。<br>
 <syntaxhighlight lang="c#">
 using System;
 
 class Complex
 {
    public double Re { get; set; }
    public double Im { get; set; }
 
    public double Abs
    {
       get { return Math.Sqrt(Re * Re + Im * Im); }
    }
 }
 </source>
<br>
===== get-onlyプロパティ =====
C# 6では、getアクセサだけのプロパティを定義できる。<br>
この場合、コンストラクタ内のみ値を代入できる。(以降、書き換え不可になる)<br>
 <syntaxhighlight lang="c#">
 using System;
 
 class Complex
 {
    public double Re { get; }
    public double Im { get; }
 
    public Complex(double re, double im)
    {
       // コンストラクタ内のみ代入可能
       Re = re;
       Im = im;
    }
 }
 </source>
<br>
上記のサンプルコドは、以下のような意味になる。(readonlyのバックフィルドが作成される)<br>
 <syntaxhighlight lang="c#">
 using System;
 
 class Complex
 {
    private readonly double _re;
    private readonly double _im;
 
    public double Re { get { return _re; } }
    public double Im { get { return _im; } }
 
    public Complex(double re, double im)
    {
       // コンストラクタ内のみ代入可能
       _re = re;
       _im = im;
    }
 }
 </source>
<br>
===== プロパティ初期化子 =====
同じくC# 6.0から、自動プロパティに対して初期化子を与えることができる。<br>
これにより、コンストラクタに記述しなくとも、プロパティに初期値を与えることができる。<br>
 <syntaxhighlight lang="c#">
 class Point
 {
    public int X { get; set; } = 10;
    public int Y { get; set; } = 20;
 }
 </source>
<br>
===== expression-bodiedなプロパティ =====
get-onlyのプロパティに限られるが、他のいくつかのメンバ関数と同様に、expression-bodied(本体が式の)形式でプロパティを定義できる。<br>
上記の複素数クラスでは、Absプロパティの定義が簡潔に記述できる。<br>
 <syntaxhighlight lang="c#">
 using static System.Math;
 
 class Complex
 {
    public double Re { get; set; }
    public double Im { get; set; }
 
    public double Abs => Sqrt(Re * Re + Im * Im);
 }
 </source>
<br><br>

==  注意点 ==
C#には、インデックス付きプロパティは存在しない。<br>
C#では、コレクションクラスを返すプロパティが推奨される。<br>
<br>
また、配列やICollection型を返す場合は、IEnumerable型を返すべきである。<br>
 <syntaxhighlight lang="c#">
 // 配列を返す場合
 
 // コンパイル可能
 int[] x;
 public int[] X
 {
    get => x;
 }
 
 // コンパイルエラー
 //int[] x;
 //public int X[int i]
 //{
 //   get => x[i];
 //   private set => x[i] = value;
 //}
 
 private List<int> listItems;  // Itemsプロパティのフィールド
 [System.Runtime.CompilerServices.IndexerName("Items")]
 public int this[int index]    // インデクサ
 {
    get => listItems[index];
    set => listItems[index] = value;
 }
 
 // IEnumerableを返す場合
 int[] x;
 public IEnumerable<int> X
 {
    get => {
       foreach (var item in x) 
       {
          yield return item;
       }
    }
 }


また、.NET Frameworkは、インデックス付きプロパティを認めている。(VB.NETはインデックス付きプロパティが存在する)
C#から呼び出す場合は、get_***というような名前のメソッドを呼び出す。
例えば、VB.NETにてint X[int i]と言う名前でインデックス付きプロパティを定義した場合、 C#からはget_X(0)というように呼び出す。

更に特殊な事情として、COMの場合のみ、X[0]というような呼び出し方法が認められる。
詳しくは、COM相互運用時の特別処理を参照すること。