読み取り専用オブジェクトを返すベストプラクティス


11

C#でのOOPに関する「ベストプラクティス」の質問があります(ただし、すべての言語に当てはまります)。

たとえば、プロパティアクセサを介して、パブリックに公開されるオブジェクトを含むライブラリクラスを持つことを検討してください。

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

明らかに最適なアプローチは「オプションC」ですが、ReadOnlyバリアントを持つオブジェクトはごくわずかです(そして確かにユーザー定義クラスにはありません)。

あなたがクラスAのユーザーである場合、あなたは

List<string> someList = ( new A() ).Items;

元の(A)オブジェクトに伝搬しますか?または、コメント/ドキュメントに記述されているクローンを返すことはできますか?このクローンアプローチは、追跡が非常に難しいバグにつながる可能性があると思います。

C ++ではconstオブジェクトを返すことができ、constとしてマークされたメソッドしか呼び出せないことを覚えています。C#にはそのような機能/パターンはないのでしょうか?そうでない場合、なぜ含まれないのですか?const constnessと呼ばれると思います。

しかし、再び私の主な質問は、「何を期待しますか」またはオプションAとBの処理方法についてです。


2
.NET 4.5の新しい不変コレクションのプレビューに関するこの記事をご覧ください。
クリストフクレス

回答:


10

コレクションの最良の解決策である@Jesseの答えに加えて、一般的なアイデアは、Aのパブリックインターフェイスを、それだけが返されるように設計することです。

  • 値型(int、double、structなど)

  • 不変オブジェクト( "string"など、またはクラスAと同様に読み取り専用メソッドのみを提供するユーザー定義クラス)

  • (必要な場合のみ):「オプションB」が示すように、オブジェクトのコピー

IEnumerable<immutable_type_here> それ自体は不変であるため、これは上記の特別な場合にすぎません。


19

また、インターフェイスにプログラミングする必要があることに注意してくださいIEnumerable<string>。概して、そのプロパティから返されるものはです。A List<string>は一般的に可変であるため、A は少しノーです。これはReadOnlyCollection<string>、実装者としてICollection<string>はLiskov Substitution Principleに違反しているように感じられると言っています。


6
+1、IEnumerable<string>ここで必要なのはまさに使用することです。
Doc Brown

2
.Net 4.5では、を使用することもできますIReadOnlyList<T>
svick

3
他の利害関係者への注意。プロパティアクセサーを検討する場合:return _listを実行してList <string>ではなくIEnumerable <string>を返す場合(+ IEnumerableに暗黙的にキャスト)、このリストをList <string>にキャストして変更すると、変更が変更されますメインオブジェクトに伝播します。内部リストを保護する新しいList <string>(_list)(+ IEnumerableへの後続の暗黙的なキャスト)を返すことができます。詳細についてはこちらをご覧ください:stackoverflow.com/questions/4056013/…–
NeverStopLearning

1
インデックスでアクセスする必要があるコレクションの受信者は、それを容易にする型にデータをコピーする準備をする必要がありますか、コレクションを返すコードがそれを提供する形式でそれを提供することを要求する方が良いですか?インデックスでアクセスできますか?サプライヤがインデックスによるアクセスを容易にしない形式で提供する可能性が高い場合を除き、受信者が独自の冗長コピーを作成する必要性から解放することの価値は、サプライヤが提供する必要がないことの価値を上回ると考えますランダムアクセス形態(例えばA読み取り専用IList<T>
supercat

2
IEnumerableについて気に入らないことの1つは、コルーチンとして実行されるのか、単なるデータオブジェクトであるのかがわからないことです。コルーチンとして実行すると、微妙なバグが発生する可能性があるため、時々知っておくと良いでしょう。私はReadOnlyCollectionまたはIReadOnlyListなどを好む傾向にある理由はここにある
スティーブ・バーミューレン

3

しばらく前と同様の問題に遭遇しましたが、それは私の解決策でしたが、実際にはライブラリを制御する場合にのみ機能します:


クラスから始めましょう A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

次に、プロパティのget部分(および読み取りメソッド)のみを使用して、これからインターフェイスを派生させ、Aにそれを実装させます。

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

もちろん、読み取り専用バージョンを使用する場合は、単純なキャストで十分です。これはまさにあなたのソリューションCが何であるかですが、私はそれを達成した方法を提供すると思いました


2
これに関する問題は、それを可変バージョンに戻すことができるということです。また、LSPの違反である基本クラス(この場合はインターフェイス)のメソッドを介して観察可能な状態は、サブクラスの新しいメソッドによって変更できます。しかし、たとえそれが強制されていなくても、少なくとも意図を伝えます。
user470365

1

この質問を見つけて、まだ答えに興味がある人のために-今、さらされている別のオプションがありますImmutableArray<T>。私はそれがその後、より良いことだと思う理由は、これらはいくつかのポイントですIEnumerable<T>ReadOnlyCollection<T>

  • 不変であることを明確に述べています(表現力豊かなAPI)
  • コレクションが既に形成されていることを明確に示しています(つまり、遅延的に初期化されておらず、何度も繰り返し処理してもかまいません)
  • アイテムの数がわかっている場合のように、それはそのknowlegdeを隠していないIEnumerable<T>
  • クライアント以来の実装が何であるかを知らないIEnumerableか、IReadOnlyCollect彼/彼女はそれが(他の言葉で、そのコレクションへの参照滞在変わらない一方で、コレクションのアイテムが変更されていないという保証はありません)決して変わらないと仮定することはできません

また、APiがコレクションの変更についてクライアントに通知する必要がある場合は、別のメンバーとしてイベントを公開します。

IEnumerable<T>一般的に悪いことではないことに注意してください。コレクションを引数として渡すとき、または遅延初期化されたコレクションを返すときに、まだ使用します(この特定のケースでは、アイテム数はまだわかっていないため非表示にするのが理にかなっています)。

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