1. ホーム
  2. c#

[解決済み] IEnumerable vs IReadonlyCollection vs ReadonlyCollection (リストメンバーを公開する場合)

2023-04-14 21:13:53

質問

私は、リストのメンバーを公開するというテーマについて、かなりの時間を費やして熟考してきました。私と同じような質問で、Jon Skeet が素晴らしい回答をしました。どうぞ、お気軽にご覧ください。

メンバーコレクションを公開するための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);
    }
}

上記はキャストやミューテートが可能ですが、オブジェクトはまだイミュータブルです。

もうひとつの枠にはまらない発言は、コレクションクラスでしょう。次のように考えてみてください。

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を思い通りに変異させるためのメソッドを持つことができますが、あなたのオブジェクトは決してあらゆる種類のリストにキャストされて変異させられることはありません。

Carsten FührmannはIEnumerablesのyield returnステートメントについて素晴らしい指摘をしています。

皆さん、改めてありがとうございました。

どのように解決するのですか?

クラスライブラリの話をすると、IReadOnly*は本当に便利だと思うし、あなたはそれを正しく実行していると思う :)

イミュータブルコレクションのことですね...。以前はimmutablesしかなく、配列を拡大するのは大変な作業でした。そこで.netはフレームワークに何か違うもの、つまりmutableコレクションを含めることにしました。

objective-cのような他の今日の言語をチェックすると、実際にはルールが完全に逆転していることがわかります! 言い換えれば、インターフェイスは単にイミュータブルを公開し、内部ではミュータブルコレクションを使用します(もちろん、それはあります)。

というわけで、他の言語でのちょっとした経験から、.netのリストはとても強力だが、immutableコレクションは何らかの理由でそこにあったのだと思うようになりました:)

この場合、インターフェイスの呼び出し元を助ける問題ではなく、IList対Listのように、内部実装を変更する場合にすべてのコードを変更することを避けるために、IReadOnly*であなた自身、あなたのクラスを適切ではない方法で使用されないように保護しています。