プロパティとメソッドの選択

2022年2月10日

一般に、メソッドはアクションを表し、プロパティはデータを表します。プロパティはフィールドのように使用されます。つまり、プロパティは計算上複雑であったり、副作用が発生したりしないようにする必要があります。次のガイドラインに違反しない場合は、メソッドではなくプロパティの使用を検討してください。経験の浅い開発者は、プロパティの方が使いやすいと考えるからです。

プロパティを使用するケース

  • 型が論理属性(bool)を表す場合は、プロパティの使用を検討してください。
  • プロパティの値がプロセスメモリに格納され、プロパティが値へのアクセスのみを提供する場合は、メソッドではなくプロパティを使用してください。

次のサンプルはメソッドですが、プロパティにすべきです。

ケース1

private int hp;
private int mp;
public int GetHp()
{
    return hp;
}
public void SetHp(int value)
{
    hp = value;
}
public int GetMp()
{
    return mp;
}
public void SetMp(int value)
{
    mp = value;
}

ケース2

private int hp;
private int mp;
public void SetHp(int hp)
{
    this.hp = hp;
}
public void SetMp(int mp)
{
    this.mp = mp;
}

プロパティに変更

展開したコード

class Player
{
    private int hp;
    private int mp;
    public int Hp
    {
        get
        {
            return hp;
        }
        set
        {
            hp = value;
        }
    }
    public int Mp
    {
        get
        {
            return mp;
        }
        set
        {
            mp = value;
        }
    }
}

短縮したコード

class Player
{
    private int hp;
    private int mp;
    public int Hp { get => hp; set => hp = value; }
    public int Mp { get => mp; set => mp = value; }
}

自動プロパティ(C#6からこのように省略可能)

class Player
{
    public int Hp { get; set; }
    public int Mp { get; set; }
}

自動プロパティ(コンパイラ内部での処理)

自動プロパティは、プログラマにはシンプルに扱えて便利ですね
ただ、内部での処理は次のようになっていて、人の代わりにメンバーを追加しています
なお、この補完されたメンバー(<Hp>__BackingFieldなど)は、人が参照することはできません

internal class Player
{
    // [コンパイラが作成]
    private int <Hp>k__BackingField;

    // [コンパイラが作成]
    private int <Mp>k__BackingField;

    public int Hp
    {
        // [コンパイラが作成]
        get
        {
            return <Hp>k__BackingField;
        }
        // [コンパイラが作成]
        set
        {
            <Hp>k__BackingField = value;
        }
    }

    public int Mp
    {
        // [コンパイラが作成]
        get
        {
            return <Mp>k__BackingField;
        }
        // [コンパイラが作成]
        set
        {
            <Mp>k__BackingField = value;
        }
    }
}

メソッドを使用するケース

プロパティはフィールドよりも桁違いに低速です。スレッドのブロックを回避するために非同期バージョンの操作を提供することを検討している場合でも、プロパティとは遅すぎる可能性があります。特に、ネットワークやファイルシステムにアクセスする操作(初期化のために1回以外)は、プロパティではなくメソッドにすべきです。

引数から計算して結果を返す場合

キロメートルをメートルに変換するサンプルなど

public int KiloMeterToMeter(int meter)
{
    return meter * 1000;
}

呼び出されるたびに値が変わる

呼び出されるたびに異なる結果を返す場合は、メソッドの方がいいです

int hpCount;
public  int Count()
{
    return hpCount++;
}

型が配列型

参照型の場合、読み込み専用にしていても、値を変更できてしまいます

そのような場合、次のようにメソッドで読み出すようにします

int[] hpCount = new int[] { 1, 2, 3, 4, 5 };
public int[] Count()
{
    return hpCount;
}

ちなみにプロパティの場合、次のようになります

int[] hpCount = new int[] { 1, 2, 3, 4, 5 };

public int[] Count
{
    get
    {
        return hpCount;
    }
}

読み出すサンプルは次のようになります
ここで着目してもらいたいのが、読み取りオンリーを期待したのに、書き換えができてしまうところです
myClass.Count[3] = 3;がエラーになりません

なので、このような場合、思わぬ挙動になりますので、メソッドで実装する方がいいです

using System;

namespace Property_Array
{
    class Program
    {

        static void Main(string[] args)
        {
            MyClass myClass = new();
            myClass.Count[3] = 3;

            foreach (var item in myClass.Count)
            {
                Console.WriteLine(item);
            }
        }
    }

    class MyClass
    {
        int[] hpCount = new int[] { 1, 2, 3, 4, 5 };

        public int[] Count
        {
            get
            {
                return hpCount;
            }
        }
    }
}
実行結果
1
2
3
3
5

Press any key to continue...

参考

C#

Posted by hidepon