C#でクラスを作ろう(4)/プロパティ

3月 28, 2020

このシリーズについて

C言語やC++言語などを学んではいるけどクラスをあまり作ったことが無い、という方を対象にしています。
このシリーズでは、C#でクラスを作るための基本的な構文を解説しています。C++やJavaなどと共通している概念も多いですが、サンプルコードは基本的にC#で解説します。ところどころ、C++特有の概念を解説することもあります。

スポンサーリンク

getterとsetter

商品を表わすこんなクラスがあるとします。

class Item
{
    private string _name;
    private int _price;
    private double _taxRate;
}

このままではprivateなフィールドに外部から一切アクセスできません。そこで、privateなフィールドを読み書きするgetter(ゲッター)とsetter(セッター)と呼ばれる特殊なメソッドを定義します。

class Item
{
    private string _name;
    public string GetName()
    {
        return _name;
    }
    public void SetName(string name)
    {
        _name = name;
    }
 
    private int _price;
    public int GetPrice()
    {
        return _price;
    }
    public void SetPrice(int price)
    {
        _price = price;
    }
 
    private double _taxRate;
    public double GetTaxRate()
    {
        return _taxRate;
    }
    public void SetTaxRate(double taxRate)
    {
        _taxRate = taxRate;
    }    
}
 
//使い方
Item item = new Item();
item.SetPrice(5000);
Console.WriteLine($"値段は{item.GetPrice()}円です");
//出力結果:値段は5000円です

“GetXXX"というメソッドがgetterで、"SetXXX"というメソッドがsetterです。クラス内部のprivateなフィールドの読み書きは、必ずgetterとsetterを使って行います。

何をしてるんだ? と思われるかもしれません。こんなことするくらいなら、最初からフィールドをpublicにすればいいじゃん、と。この例の場合は、ぶっちゃけその通りです。

getterとsetterの利点

さっきの例では、ぶっちゃけ利点はありません。でも、getterもsetterもメソッドなので、値の読み書き以上の処理を加えることができるという利点があります。どんなときにその利点が生きてくるのか、いくつか例を見てみましょう。

読み取り専用、書き込み専用にできる

private string _name;
public string GetName()
{
    return _name;
}

このように、getterだけ定義してsetterを定義しなければ、外部からはフィールド_nameを読み取り専用にできます。外部から値を変更されたくないときは、このようにしてgetterだけを定義して読み取り専用にします。クラスの内部からはprivateなフィールドへの書き込みもできるので、内部での値の変更は自由です。

逆にsetterだけを定義してgetterを定義しなければ、書き込み専用にすることができます。

値のチェックができる

private int _price;
public int GetPrice()
{
    return _price;
}
public void SetPrice(int price)
{
    if (price < 0)
        throw new Exception();
    _price = price;
}

商品の値段がマイナスになってはいけません。そこで、値段priceを書き込むsetter内で、値のチェックを行うようにします。もしマイナスの値を入れようとした場合は、例外を発生させるようにします。

他に独自のエラー処理機構を用意している場合は、例外を発生させる以外の方法でもかまいません。とにかく、値の代入時にすばやく異常値を検知しておくことで、オブジェクトの状態が不正になることを防ぎます。

クラス内部とは別の表現方法で読み書きを行う

private double _taxRate;
public double GetTaxPercent()
{
    return _taxRate * 100;
}
public void SetTaxPercent(double taxPercent)
{
    _taxRate = taxPercent / 100;
}
 
//使い方
Item item = new Item();
item.SetTaxPercent(8); //内部の_taxRateフィールドは0.08になる

クラス内部の_taxRateフィールドは、消費税率(8%なら0.08)を保持しているものとします。計算上、単位をパーセントとするよりも、「0.08」の数字のほうが扱いやすいので、このようにしています。

しかし、クラスの外部から消費税率を設定したり取得したりする場合は、単位がパーセントのほうが扱いやすいです。そこで、内部では「率(Rate)」で保持するけども、外部から扱うときは「パーセント(Percent)」で読み書きする、というようなことがgetterとsetterを使ってできるようになります。

値の変更を通知する

private string _name;
public void SetName(string name)
{
    if (name == _name)
        return;
    _name = name;
    RaisePropertyChanged("Name");
}

MVCパターンなどでよく見かける手法です。setterによって値が変更された場合、変更されたことを別の誰かに知らせるためのRaisePropertyChangedメソッドを呼び出します。例えば、値が変更されると画面表示が自動的に変更されるような仕組みがある場合に、setterにこのような仕掛けを入れたりします。

プロパティ

フィールドに対してgetterとsetterを用意するという一連の流れは、非常によく出てきます。

getterとsetterは言語仕様的には普通のメソッドですが、よく出てくる手法なので、特別にプロパティという形で定義することができます(C#の場合)。

private string _name;
public string Name
{
    get
    {
        return _name;
    }
    set
    {
        _name = value;
    }
}
 
//使い方
Item item = new Item();
item.Name = "りんご";
Console.WriteLine($"商品名は{item.Name}");
//出力結果:商品名はりんご

ここで定義された"Name"を、プロパティと呼びます。

“get"と"set"、そして"value"は特別なキーワードです。このような書き方をすると、getterとsetterがそれぞれ定義され、外部から使うときはメソッド呼び出しの()を使わずに、フィールドに読み書きするかのような構文で使うことができます。

そしてさらに、この例のようにgetterにもsetterにも追加処理が無い単なる読み書きだけのパターンの場合、もっと記述を省略して、

public string Name { get; set; }

とすることができます。この記法を、自動実装プロパティと呼びます。

getterの中身も省略、setterの中身も省略、そして何より、プロパティの内側に実体として存在していたフィールド"_name"さえもが省略されています。ちなみに、プロパティの内側に実体として存在するフィールドのことをバッキングフィールドと呼びます。

この一番省略された自動実装プロパティの記法のもとで読み取り専用にしたい場合は、

public string Name { get; }                //クラスの内側からも書き込み不可
public string Name { get; private set; }   //クラスの内側からは書き込みもできる

のようにします。

プロパティかフィールドか

もともとは、クラスの内側に存在する内部のフィールドを情報隠蔽という考え方のもとにprivateにし、外側から読み書きをするために特別なメソッドであるgetterとsetterを定義したのでした。

そして、GetXXXとかSetXXXとかいうメソッドを呼ぶのは面倒なので、プロパティという言語仕様が登場しました。

getterとsetterは拡張性があり、いろいろな付加動作を加えることができます。しかしながら、実際のクラス設計では、何も付加動作を加えない"public string Name { get; set; }"の形が多く使われます。多く使われるからこそ、このような省略記法が登場するようになったのでしょう。

この、一番省略した最後の形は、言語仕様上はプロパティではあるけども、もはやこれはpublicなフィールドとなんら変わりがありません。であるなら、getterだのsetterだのプロパティだのと難しいことを考えずに、最初からpublicなフィールドでよかったんじゃないか、という考え方にも一理あります。

クラスの内部状態を公開するには常にプロパティを使うべし、というクラス設計の考え方のほうが時代の趨勢です。情報隠蔽や拡張性という観点から見て、確かにそれは正論です。なので、最初はまずその正論に倣って、getterやsetterやプロパティというものを学んでみてください。

しかし、実際の業務でクラスをたくさん作り慣れてきた段階で、「もうこれ、publicなフィールドでいいんじゃね?」と感じるようになったら、流行とか正論とかに捉われず、publicなフィールドを使うように柔軟に思考を転換させてみるのもいいかもしれません。

このあたりの考え方の違いは、いわゆる「宗教論争」というやつです。どちらが正しいとかではなく、どちらの考え方も尊重する広い心を持つようにしましょう。

スポンサーリンク