プロパティにしたけれど、メソッドの方がよかった? .NETの「後悔話」から学ぶAPI設計

広告

C#を勉強していると、プロパティとメソッドが登場します。

例えば、配列の要素数を取得するときは、

int[] scores = { 80, 70, 90 };

Console.WriteLine(scores.Length);

と書きます。

Lengthには()がありません。

つまり、Lengthはメソッドではなくプロパティです。

では、なぜ、

scores.GetLength()

ではないのでしょうか?

今回はここから一歩踏み込んで、

「プロパティにするべきか、メソッドにするべきか」

というAPI設計の話を見てみましょう。

そして.NETには、この設計について開発者から「メソッドの方がよかったのでは」と指摘された有名な例があります。


まずはプロパティとメソッドを確認しよう

例えば、次のようなPersonクラスがあったとします。

class Person
{
    public string Name { get; set; }

    public void SayHello()
    {
        Console.WriteLine("こんにちは");
    }
}

使う側は、

person.Name

と、

person.SayHello()

のように書きます。

見た目にも違いがあります。

Name
    ↓
プロパティ
「その人が持っている情報・状態」

SayHello()
    ↓
メソッド
「その人に何かをさせる」

プロパティは名詞的です。

メソッドは動詞的です。

これが基本的な考え方です。


配列のLengthはなぜプロパティなのか

次の配列を考えます。

int[] numbers = { 10, 20, 30 };

Lengthを取得すると、

Console.WriteLine(numbers.Length);

結果は、

3

です。

ここで重要なのは、Lengthを呼ぶたびに、

10 → 1個

20 → 2個

30 → 3個

だから3!

と数えているわけではないことです。

配列には、作られた時点で長さの情報があります。

イメージすると、

numbers
   │
   ▼
┌─────────────────────┐
│ 配列の管理情報        │
│ Length = 3          │
├─────────────────────┤
│ [0] 10              │
│ [1] 20              │
│ [2] 30              │
└─────────────────────┘

となっています。

numbers.Lengthは、この長さの情報を取得しています。

つまり、

numbers.Length

は、

「配列の長さを計算する」

というより、

「この配列が持っている長さという情報を読む」

という感覚です。

そのため、プロパティとして非常に自然です。


プロパティは「フィールドのように見える」

.NETの設計ガイドラインでは、プロパティはしばしば**smart field(スマートなフィールド)**のようなものとして説明されます。

例えば、

person.Name

を見たプログラマーは、

「Nameという状態を取得している」

と感じます。

同じように、

array.Length

なら、

「配列の長さという状態を取得している」

と感じます。

つまりプロパティには、

「何か大きな処理を実行しているようには見えない」

という特徴があります。

ここが重要です。


では、こんなプロパティはどうだろう?

例えば次のようなプロパティを作ったとします。

public int RandomNumber
{
    get
    {
        return Random.Shared.Next();
    }
}

そして、

Console.WriteLine(obj.RandomNumber);
Console.WriteLine(obj.RandomNumber);
Console.WriteLine(obj.RandomNumber);

とします。

すると、

135
827
42

のように、アクセスするたびに違う値になる可能性があります。

少し不思議ではないでしょうか。

見た目は、

obj.RandomNumber

なので、「値を読んでいる」ように見えます。

しかし実際には、

新しい乱数を生成する

という処理をしています。

それなら、

obj.GetRandomNumber()

あるいは、

random.Next()

のようなメソッドの方が、

「何かを実行した」

ことが分かりやすくなります。


.NETにも議論になったプロパティがある

ここで登場するのが、

DateTime.Now

です。

C#を使ったことがある人なら、一度は見たことがあるでしょう。

現在日時を取得できます。

DateTime now = DateTime.Now;

例えば、

2026/08/09 10:00:00

のような値が返ってきます。

ところが、もう一度、

DateTime now2 = DateTime.Now;

とすると、

2026/08/09 10:00:01

になっているかもしれません。

つまり、

DateTime.Now

はアクセスするたびに結果が変わる可能性があります。


「これはメソッドにするべきだったのでは?」

このDateTime.Nowについて、.NETのAPI設計を扱った『Framework Design Guidelines』では、Jeffrey Richterが、

DateTime.Nowはプロパティではなくメソッドにするべきだった

という趣旨のコメントをしています。

理由は、

呼び出すたびに異なる値を返すから

です。

もしメソッドとして設計するなら、例えば、

DateTime.GetNow()

のような形が考えられます。

すると、

DateTime.Now

「Nowという状態を読む」

より、

DateTime.GetNow()

「現在時刻を取得する処理を実行する」

という意味が強くなります。


元MicrosoftのC#開発者Eric Lippertも指摘している

元MicrosoftのC#コンパイラ開発者Eric Lippertも、プロパティ設計についての記事の中でDateTime.Nowを取り上げています。

プロパティは基本的に、オブジェクトの状態・特性として自然であり、比較的安定した値として見えることが望ましいという考え方です。

その観点では、

DateTime.Now

は少し特殊です。

何も代入していないのに、

DateTime.Now

を読むだけで、

10:00:00
   ↓
10:00:01
   ↓
10:00:02

と値が変わっていくからです。


Guid.NewGuid()と比較すると分かりやすい

さらに面白い比較があります。

新しいGUIDを生成するときは、

Guid.NewGuid()

と書きます。

こちらはメソッドです。

Guid.NewGuid();
Guid.NewGuid();
Guid.NewGuid();

実行するたび、新しいGUIDが生成されます。

つまり、

Guid.NewGuid()
       ↓
「新しい値を生成する」
       ↓
メソッド

です。

一方、

DateTime.Now
       ↓
「現在時刻を取得する」
       ↓
プロパティ

です。

どちらもアクセスするたび結果が変わる可能性があります。

そのため、

DateTime.Now

もメソッドにした方が、API設計として一貫していたのではないか、という議論が生まれるわけです。


では、後から直せばいいのでは?

ここで、

「それなら.NETのバージョンアップで直せばいいのでは?」

と思うかもしれません。

しかし、そう簡単ではありません。

世界中には、

DateTime.Now

を使ったプログラムが大量に存在します。

もし突然、

DateTime.Now

を廃止して、

DateTime.GetNow()

に変更したら、既存プログラムがコンパイルできなくなる可能性があります。

これは後方互換性の問題です。

一度公開されたAPIは、簡単には変更できません。


拡張メソッドで直せないの?

C#には拡張メソッドがあります。

例えば、

string text = "Hello";

text.SomeMethod();

のように、既存の型にメソッドを追加したように見せることができます。

では、

DateTime.GetNow()

を拡張メソッドで追加すればよいのでしょうか?

残念ながら、通常の拡張メソッドではこの形にはできません。

拡張メソッドは基本的に、

someObject.ExtensionMethod()

というインスタンスに対する呼び出しだからです。

既存のDateTime型そのものに、

DateTime.GetNow()

という新しい静的メソッドを後付けすることはできません。


自分のプログラムならラッパーを作れる

自分でAPIを設計するなら方法があります。

例えば、

public static class Clock
{
    public static DateTime GetNow()
    {
        return DateTime.Now;
    }
}

とすれば、

DateTime now = Clock.GetNow();

と書けます。

これなら、

Clock.GetNow()
      ↓
現在時刻を「取得する」
      ↓
メソッド

という設計にできます。

現在の.NETにはTimeProviderという、時間を扱いやすくするための仕組みもあります。


「値が変わるならメソッド」は絶対ではない

ここで注意してください。

値が変化するプロパティはすべて間違い

という意味ではありません。

例えば、

person.Age

は時間が経てば変わります。

car.Speed

も車が走れば変わります。

しかし、どちらも、

そのオブジェクトの「現在の状態」

として自然です。

そのためプロパティとして違和感はありません。

問題になるのは、

アクセスしたこと自体によって新しい結果を生成したり、何らかの処理を実行しているような場合

です。


プロパティとメソッドをどう使い分ける?

初心者のうちは、次のように考えると分かりやすいでしょう。

考え方向いているもの
「どんな状態?」プロパティ
「どんな特徴?」プロパティ
「何を持っている?」プロパティ
「何をする?」メソッド
「計算して」メソッド
「生成して」メソッド
「検索して」メソッド
「測定して」メソッド

例えば、

array.Length

は、

「この配列の長さは?」

なのでプロパティ。

Guid.NewGuid()

は、

「新しいGUIDを作って」

なのでメソッドです。


API設計は「使う人にどう見えるか」まで考える

今回の話で重要なのは、

プロパティ → ()がない

メソッド → ()がある

という文法だけではありません。

APIを設計するときには、

そのコードを読んだ人が、どんな処理だと想像するか

まで考えます。

obj.Value

を見れば、

「値を読んでいるだけだろう」

と思います。

一方、

obj.CalculateValue()

なら、

「何か計算するんだな」

と想像できます。

優れたAPIは、コードそのものが説明書になります。


まとめ

配列の、

array.Length

は、配列がすでに持っている長さという情報を取得するため、プロパティとして自然です。

一方、

DateTime.Now

については、アクセスするたびに異なる値になるという性質から、「メソッドとして設計した方がよかった」という指摘が.NETのAPI設計の世界で実際にあります。

ここから学べるのは、

「動くかどうか」だけが良いプログラムの基準ではない

ということです。

同じ機能でも、

obj.Value

と、

obj.GetValue()

では、コードを読む人に与える意味が違います。

C#でクラスを作るようになったら、

これは「そのオブジェクトが持っている状態」なのか?

それとも「何かを実行して得る結果」なのか?

と考えてみてください。

この問いが、プロパティとメソッドを設計するときの大切な判断基準になります。

訪問数 8 回, 今日の訪問数 8回

広告

C#

Posted by hidepon