web-dev-qa-db-ja.com

リストメンバーを公開するためのIEnumerable対IReadonlyCollection vs ReadonlyCollection

リストメンバーを公開するというテーマについて熟考しました。私と同様の質問で、ジョン・スキートはすばらしい答えを出しました。ご覧ください。

ReadOnlyCollectionまたはメンバーコレクションを公開するためのIEnumerable?

リストを公開することは、特にAPIを開発している場合は特に非常に妄想的です。

リストを公開するためにIEnumerableを使用しました。これは非常に安全であり、柔軟性が高いためです。ここで例を使用します

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

IEnumerableに対してコーディングする人は誰でもここで非常に安全です。後で順序付きリストまたは何かを使用することにした場合、それらのコードは壊れず、それでもナイスです。この欠点は、IEnumerableをこのクラスの外部のリストにキャストできることです。

このため、多くの開発者は、ReadOnlyCollectionを使用してメンバーを公開しています。リストに戻すことはできないため、これは非常に安全です。私はIEnumerableの方が好みです。リストとは異なる何かを実装したい場合、柔軟性が向上するからです。

私は自分がもっと好きな新しいアイデアを思いつきました。 IReadOnlyCollectionを使用する

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

これはIEnumerableの柔軟性の一部を保持し、非常にうまくカプセル化されていると思います。

この質問を投稿して、私のアイデアについて意見を述べました。 IEnumerableにこのソリューションを好むか。 ReadOnlyCollectionの具体的な戻り値を使用する方が良いと思いますか?これはかなりの議論であり、私たち全員が思い付くことができる利点/欠点は何かを試してみたいと思います。

ご意見をお寄せいただきありがとうございます。

[〜#〜] edit [〜#〜]

まず、ここでの議論に多大な貢献をしてくれてありがとう。私は確かに一人一人から多くのことを学びました。心から感謝します。

いくつかの追加のシナリオと情報を追加しています。

IReadOnlyCollectionとIEnumerableには一般的な落とし穴がいくつかあります。

以下の例を検討してください。

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

上記の例は、インターフェースが読み取り専用であっても、リストにキャストして変更できます。インターフェイスは、それが同名であるにもかかわらず、不変性を保証しません。不変のソリューションを提供するのはユーザー次第であるため、新しいReadOnlyCollectionを返す必要があります。新しいリスト(本質的にコピー)を作成することにより、オブジェクトの状態は安全で健全です。

Richibanは彼のコメントでそれを最もよく言っている:インターフェイスはそれができないことではなく、何ができるかを保証するだけである

例については、以下を参照してください。

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

上記はキャストおよび変更できますが、オブジェクトは不変です。

ボックスステートメントの外側にあるもう1つのコレクションクラスです。以下を考慮してください。

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

上記のクラスには、fooを希望どおりに変更するメソッドを含めることができますが、オブジェクトをあらゆる種類のリストにキャストして変更することはできません。

CarstenFührmannは、IEnumerablesのyield returnステートメントについて素晴らしい点を述べています。

改めてありがとうございます。

53
Marcel-Is-Hier

クラスライブラリについて言えば、IReadOnly *は本当に便利だと思います。あなたはそれを正しくやっていると思います:)

それはすべて不変のコレクションに関するものです...不変なものが存在する前に、配列を拡大することは大きな作業であったため、.netはフレームワークに、あなたのためにいものを実装する異なる、可変のコレクションを含めることを決めましたが、特に、変更可能なものを共有することが常にPITAである同時実行性の高いシナリオでは、非常に役立つ不変の適切な方向性を示します。

Objective-Cなどの他の今日の言語をチェックすると、実際にはルールが完全に逆になっていることがわかります。彼らは常に異なるクラス間で不変のコレクションを交換します。言い換えると、インターフェースは不変のコレクションを公開し、内部で可変コレクションを使用します(もちろん、持っています)代わりに、部外者にコレクションを変更させたい場合は適切なメソッドを公開します(クラスがステートフルクラスの場合)。

したがって、他の言語で得たこの小さな経験から、.netリストは非常に強力であると考えるようになりますが、不変のコレクションは何らかの理由でそこにありました:)

この場合、IList vs Listのように内部実装を変更する場合にすべてのコードを変更することを避けるために、インターフェイスの呼び出し元を支援することは問題ではありませんが、IReadOnly *を使用すると、自分を保護します。クラス、適切な方法で使用されない、無用な保護コードを回避するために、時にはあなたが書くこともできないコード(過去には、この問題を回避するために完全なリストのクローンを返さなければならなかったコードの一部で) 。

16
Michael Denny

これまでの答えには、重要な側面が1つありません。

IEnumerable<T>が呼び出し元に返されるとき、返されるオブジェクトが「遅延ストリーム」である可能性を考慮する必要があります。 「利回りリターン」で構築されたコレクション。つまり、IEnumerableを使用するたびに、IEnumerable<T>の要素を生成するためのパフォーマンスのペナルティを呼び出し元が支払う必要がある場合があります。 (生産性ツール「Resharper」は、実際にこれをコードの匂いとして指摘しています。)

対照的に、IReadOnlyCollection<T>は、遅延評価がないことを呼び出し元に通知します。 (IEnumerable<T>Countextension method とは対照的に、Countプロパティ(IReadOnlyCollection<T>によって継承されるため、メソッドも同様)、怠lazでないことを示します。また、IReadOnlyCollectionの遅延実装はないようです。

これは、IReadOnlyCollection<T>の代わりにIEnumerable<T>を要求すると、メソッドがコレクションに対して数回反復する必要があることを通知するため、入力パラメーターにも有効です。メソッドがIEnumerable<T>から独自のリストを作成し、それを反復処理できることは確かですが、呼び出し元は既にロードされたコレクションを手元に持っている可能性があるため、可能な限りそれを利用するのが理にかなっています。呼び出し元が手元にIEnumerable<T>しかない場合は、.ToArray()または.ToList()をパラメーターに追加するだけです。

IReadOnlyCollectionが行うことnotは、呼び出し元が他のコレクションタイプにキャストできないようにします。そのような保護のためには、classReadOnlyCollection<T>を使用する必要があります。

要約すると、onlyIReadOnlyCollection<T>IEnumerable<T>に対して行うことは、Countプロパティを追加することであるため、遅延が関係していないことを示します。

63

適切なインターフェイスを返すことができるようです。

...
    private readonly List<WorkItem> workItems = new List<WorkItem>();

    // Usually, there's no need the property to be virtual 
    public virtual IReadOnlyList<WorkItem> WorkItems {
      get {
        return workItems;
      }
    }
...

workItemsフィールドは実際にはList<T>私見の自然な考え方は、最も広いインターフェースIReadOnlyList<T> その場合

4
Dmitry Bychenko