不変性を保証することは、プロパティではなくフィールドを公開する正当な理由ですか?


10

C#の一般的なガイダンスは、常にパブリックフィールドではなくプロパティを使用することです。これは理にかなっています。フィールドを公開することで、実装の詳細の多くを公開することになります。プロパティを使用すると、その詳細をカプセル化して、コードを消費しないようにし、実装の変更をインターフェイスの変更から切り離します。

ただし、readonlyキーワードを処理するときに、このルールに有効な例外が時々あるかどうか疑問に思っています。このキーワードをパブリックフィールドに適用すると、不変性が保証されます。これは単なる実装の詳細ではなく、不変性は消費者が関心を持つ可能性のあるものです。readonlyフィールドを使用すると、フィールドがパブリックコントラクトの一部になり、パブリックインターフェースを変更することなく、将来の変更または継承によって破壊されないものになります。プロパティが提供できないものです。

それでは、不変性を保証することが、readonly場合によってはプロパティよりもフィールドを選択する正当な理由でしょうか?

(明確にするために、クラスの設計の一部として意味があり、契約に不変性を含めることが意図されている場合にのみ、現時点でフィールドが不変であるという理由だけで、常にこの選択を行うべきだと言っているわけではありません。私は主に、メンバーがにある必要があるinterface場合や遅延読み込みを行う場合など、正当化できない特定のケースではなく、これが正当化されるかどうかに焦点を当てた回答に関心があります。)



1
@gnat明確にするために、「読み取り専用プロパティ」では、VB.NETの用語を使用しています。これは、読み取り専用フィールドではなく、セッターのないプロパティを意味します。私の質問では、その質問が比較する2つのオプションは同等です。どちらもプロパティを使用しています。
Ben Aaronson、2015

回答:


6

パブリック静的読み取り専用フィールドは確かに問題ありません。名前付きの定数オブジェクトが必要だが、constキーワードを使用できない場合など、特定の状況で推奨されます。

読み取り専用のメンバーフィールドは少しトリッキーです。パブリックセッターがなければ、プロパティに勝る利点は多くありません。また、大きな欠点があります。フィールドをインターフェイスのメンバーにすることはできません。そのため、具象型ではなくインターフェイスに対して操作するようにコードをリファクタリングする場合は、読み取り専用フィールドをパブリックゲッターのプロパティに変更する必要があります。

継承されたクラスがバッキングフィールドにアクセスできない限り、保護されたセッターのないパブリック非仮想プロパティは継承に対する保護を提供します。基本クラスでそのコントラクトを完全に実施できます。

この場合、クラスのパブリックインターフェイスを変更せずにメンバーの「不変性」を変更できないことは、それほど大きな障害にはなりません。通常、パブリックフィールドをプロパティに変更する場合、対処する必要がある2つのケースがあることを除いて、スムーズに進みます。

  1. のように、そのオブジェクトのメンバーに書き込むものはすべて、がフィールドでobject.Location.X = 4ある場合にのみ可能ですLocation。ただし、readonly構造体は変更できないため、これはreadonlyフィールドには関係ありません。(これはLocation値型であると想定しています。そうでない場合、readonlyはそのようなことから保護されないため、この問題はとにかく適用されません。)
  2. outまたはとして値を渡すメソッドへの呼び出しref。この場合もoutrefパラメータは変更される傾向があるため、これは実際には読み取り専用フィールドには関係ありません。読み取り専用の値をoutまたはrefとして渡すと、コンパイラエラーになります。

つまり、読み取り専用フィールドを読み取り専用プロパティに変換するとバイナリ互換性が失われますが、他のソースコードは、この変更を処理するために変更を加えずに再コンパイルするだけで済みます。これにより、保証が破られやすくなります。

readonlyメンバーフィールドには何の利点も見当たらず、インターフェイスでそのようなことを行うことができないことは、私にとってそれを使用しないほどの欠点です。


関心のうち、なぜされているのpublic staticは読み取り専用大丈夫フィールド?インスタンスメソッドを除外すると、不変フィールドのプロパティのほとんどのユースケース(ヒットカウント、レイジーロードなど)が削除されるからですか?
Ben Aaronson、2015

パブリックの読み取り専用フィールドと読み取り専用プロパティの主な欠点はなくなりました-静的メンバーをインターフェイスの一部にすることはできないため、同様の基盤になります。読み取り専用の静的プロパティは、初期化されるストレージを必要とするか、呼び出されるたびに戻り値を再構築する必要があります。一方、読み取り専用フィールドの構文は単純で、静的コンストラクターによって初期化されます。したがって、パブリックな静的な読み取り専用フィールドは、JITによる最適化の可能性があると思います。フィールドを公開することによる通常の欠点はありません。
エリック

2

私の経験では、readonlyキーワードは約束された魔法ではありません。

たとえば、コンストラクターが以前は単純であったクラスがあるとします。使用法(およびクラスのプロパティ/フィールドのいくつかは構築後に不変であるという事実)を考えると、それは重要ではないと考えるかもしれないので、readonlyキーワードを使用しました。

その後、クラスはさらに複雑になり、そのコンストラクタも複雑になります。(これは、実験的であるか、十分な速度でスコープ/コントロールからズームアウトするプロジェクトで発生するとしますが、何らかの方法で機能させる必要があります。)readonly変更できるフィールドは、コンストラクター内でのみであることがわかります。メソッドがコンストラクタからのみ呼び出された場合でも、メソッドで変更することはできません。

この技術的な制限はC#の設計者によって認められており、将来のバージョンで修正される可能性があります。しかし、それが修正されるまで、の使用readonlyはより制限され、いくつかの悪いコーディングスタイル(すべてをコンストラクターメソッドに置く)を助長する可能性があります。

念のため、readonly非パブリックセッタープロパティも「完全に初期化されたオブジェクト」の保証も提供しません。言い換えると、「完全に初期化された」という意味と、それを不注意な使用から保護する方法を決定するのはクラスの設計者であり、そのような保護は一般に絶対確実ではありません。


1
私はコンストラクターをシンプルに保つことを好みます。brendan.enrick.com/ post / …を参照してください。blog.ploeh.dk/ 2011/03/03 / InjectionConstructors単純である必要があるため、実際には読み取り専用で適切なコーディングスタイルを推奨しています
AlexFoxGill

1
コンストラクターは、readonlyフィールドをoutまたはrefパラメーターとして他のメソッドに渡すことができます。その後、フィールドは必要に応じて自由に変更できます。
スーパーキャット2015

1

フィールドは常に実装の詳細です。実装の詳細を公開したくない。

ソフトウェアエンジニアリングの基本的な信条は、要件が将来どうなるかわからないということです。あなたのreadonly分野は今日は良いかもしれませんが、それが明日の要件の一部になることを示唆するものは何もありません。明日、そのフィールドにアクセスした回数を判断するためにヒットカウンターを実装する必要がある場合はどうなるでしょうか。サブクラスがフィールドをオーバーライドできるようにする必要がある場合はどうなりますか?

プロパティは非常に使いやすく、パブリックフィールドよりもはるかに柔軟性が高いので、c#を言語として選択し、クラスでパブリック読み取り専用フィールドを使用する単一のユースケースを考えることができません。パフォーマンスが非常に高く、余分なメソッド呼び出しの影響を受けられない場合は、おそらく別の言語を使用しているでしょう。

言語を使って何かを実行できるからといって、言語を使って何かを実行する必要があるわけではありません。

言語デザイナーがフィールドの公開を許可したことを後悔しているのではないでしょうか。コードで非constパブリックフィールドを使用することを検討した最後の時間を思い出せません。


1
私は将来の柔軟性で構築することの重要性を理解していますが、確かに、たとえば、Rationalパブリック、イミュータブルNumerator、およびDenominatorメンバーでクラスを記述した場合、それらのメンバーがレイジーロードやヒットカウンターを必要としないこと、または同様?
Ben Aaronson、2015

しかし、あなたが使用して実装することを確信することができfloatための型NumeratorとはDenominatorに変更する必要はないでしょうかdoubles
スティーブン

1
ええと、いや、彼らは間違いなく、どちらfloatでもないはずdoubleです。彼らはintlongまたはする必要がありBigIntegerます。おそらくそれらを変更する必要があるかもしれませんが、変更する場合はパブリックインターフェースでも変更する必要があるため、実際にプロパティを使用しても、その詳細の周りにカプセル化は追加されません。
Ben Aaronson、2015

-3

いいえ、それは正当化ではありません。

正当化ではなく不変性の保証としてreadonlyを使用することは、ハックに似ています。

読み取り専用は、変数が定義されたときにコンストラクターoによって値が割り当てられることを意味します。

悪い習慣になると思います

不変性を保証したい場合は、短所より長所のあるインターフェースを使用してください。

簡単なインターフェースの例: ここに画像の説明を入力してください


1
「インターフェースを使用する」方法は?
Ben Aaronson
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.