13

コンテキスト: 同僚にメールを送信し、Enumerable.Empty<T>()何もせずに空のコレクションを返す方法について説明しましreturn new List<T>();た。特定の型を公開しないという欠点があるという返信がありました。

それは小さな問題を提示します。戻り値の型についてできるだけ具体的にすることは常に良いことです* (したがって、List<> を返す場合は、それを戻り値の型にします)。これは、Lists や Arrays などに便利なメソッドが追加されているためです。さらに、呼び出し元のメソッドからコレクションを使用するときにパフォーマンスを考慮する場合にも役立ちます。

以下のトリックは、残念ながら IEnumerable を返すことを強制しますが、これは可能な限り非具体的ですよね? :(

* これは実際には .NET 設計ガイドラインからのものです。ガイドラインに記載されている理由は、私がここで言及しているものと同じだと思います。

これは、私が学んだこととは正反対のように思われ、できる限り試してみましたが、設計ガイドラインでこの正確なアドバイスを見つけることができませんでした。私はこのような小さな作品を見つけました:

Collection<T>非常に一般的ReadOnlyConnection<T>に使用されるメソッドとプロパティのサブクラスを返します。

以下にコード スニペットを示しますが、それ以上の理由はありません。


そうは言っても、これは実際に受け入れられているガイドラインですか(最初のブロック引用で説明されていた方法です)?それとも誤解されたのでしょうか?私が見つけることができる他のすべてのSOの質問にはIEnumerable<T>、戻り値の型として好ましい回答があります。元の .NET ガイドラインが古くなっているのではないでしょうか?

それとも、それほど明確ではないのでしょうか?考慮すべきトレードオフはありますか? より具体的な型を返すのが良いのはいつですか? IList<T>具体的なジェネリック型を返すこと、またはandのようなより具体的なインターフェイスのみを返すことが推奨されることはありReadOnlyCollection<T>ますか?

4

4 に答える 4

12

両方のスタイルには理由があります。

  1. 具体的に: 常にこの型を返すことを呼び出し元に保証します。実装が変更された場合でも。約束を守れますか?はいの場合は、発信者にメリットがあるように具体的に説明してください。より多くの派生型には、より多くの機能があります。
  2. 一般的であること: 問題のメソッドまたはプロパティの基になる実装を変更する可能性が高い場合は、List<T>. おそらく、IList<T>またはカスタム コレクション クラス (後で変更できます) を約束することができます。

.NET フレームワークの BCL は、配列またはカスタム コレクション クラスのいずれかと一緒に使用されます。100% 安定した API を提供する必要があるため、注意が必要です。

通常のソフトウェア プロジェクトでは、呼び出し元を変更できるので (.NET フレームワークの担当者は変更できません)、より寛容になれます。約束が多すぎて、1 年後にそれを変更する必要がある場合は、それを行うことができます (多少の努力が必要です)。

常に間違っていることが 1 つだけあります。それは、常に( 1) または (2) を実行する必要があるということです。常に常に間違っています。

特異性が明らかに正しい選択である場合の例を次に示します。

    public static T[] Slice<T>(this T[] list, int start, int count)
    {
        var result = new T[count];
        Array.Copy(list, start, result, 0, count);
        return result;
    }

可能な合理的な実装は 1 つしかないため、戻り値の型が配列であることを明確に約束できます。リストを返す必要はありません。

一方、一部の永続ストアからすべてのユーザーを返すメソッドは、内部的に大きく変わる可能性があります。データは、ストリーミングが必要な使用可能なメモリよりも大きくなる可能性があります。を選択する必要がありますIEnumerable<User>

于 2012-06-26T17:10:34.017 に答える
4

このような意思決定に関するガイドラインの問題点は、私たち開発者が何も考えずに盲目的に従う可能性があることです。これらの問題は、それぞれのケースで考えられるさまざまなアプローチの長所と短所を比較検討して、ケースバイケースで検討する必要があると私は主張します。

戻り値の型については、実際には (少なくとも) 2 つの見方があります。私が Stack Overflow で目にする哲学の 1 つは、コードの作成者であるあなたに将来の柔軟性を与えるために、よりジェネリックな型を選択することを好むことがよくあります。たとえば、 を返すメソッドを作成しIList<T>、戻り値が実際にはである場合、将来のバージョンでList<T>それを実装して返す独自の最適化されたデータ構造を後で実装する権利を留保します。IList<T>

別の、しかし私の意見では同等に有効な代替哲学は、より一般的な型を返すとライブラリの有用性が低下するため、より具体的な型を返す必要があるというものです。たとえば、常に積極的に返すIEnumerable<T>と、ライブラリのエンドユーザーが常に 、などを呼び出すシナリオにつながりますToArray()ToList()これは、同僚が「リストや配列のようなものには追加のメソッドがあり、役に立ちます。」

後者のアプローチは、「提供するものは具体的にし、受け入れるものは一般化する」という言葉で提唱されているのを時々耳にします。つまり、一般的なパラメーターを受け取りますが、特定の戻り値を提供します。この原則に同意するかどうかにかかわらず、少なくともその背後にある理由を理解するのは簡単です。

いずれにせよ、私が言ったように、各ケースは詳細が異なります。たとえば、IList<T>vsの最初の例ではList<T>、カスタム データ構造を実装することをすでに計画していて、まだそれを作成していない可能性があります。この場合List<T>、インターフェイスが数週間または数か月で異なることが既にわかっているため、 a を返すことはおそらく適切ではありません。

一方、この実装が変更される可能性が非常に低い場合でも、常に を返すメソッドがあり、T[]これを としてのみ公開することを選択する場合があります。IEnumerable<T>この場合、よりジェネリックなタイプを選択する方が美的には満足できるかもしれませんが、実際には誰にも利益をもたらす可能性は低く、実際、非常に便利な機能であるランダムアクセスの結果を奪ってしまいます。

したがって、それは常にトレードオフであり、従うことができると私が思う最良のガイドラインは、常に自分の選択を検討し、問題のケースに最も賢明な選択をするために自分の判断を使用することです.

于 2012-06-26T17:22:25.967 に答える
2

IEnumerable (または少なくとも IList) の代わりに List を返すことは、基本的にLiskov Sustitution Principleに違反します。

「List」を返すと、その型を返す必要が生じ、メソッドの内部を潜在的により効率的な実装にリファクタリングすることができなくなります (List を返すと約束しましたが、「yield」を使用する必要があります。おっと、できます外部コード チャーンなしでは実行できません)。

多くの(ほとんどの?) ケースでは、これらの種類のメソッドの主な要件は、結果を反復処理して何かを行うことです... IEnumerable は、これらの用途に正確に適しています。

ユーザーが変更可能なセットを持つ必要がある場合、これは「ToList()」または相手側の IEnumerable の他の拡張機能を使用して簡単に実現できます。

直接変更のために内部リストを公開するように要求するコードは、カプセル化にも違反している兆候である可能性があります。注意してください。私たちは、多くの関数型プログラミング言語に見られる不変性が、コードの非常に価値のある品質になることをますます発見しています。

于 2013-05-14T18:59:41.933 に答える
0

IMOそれを覚えてList<T>IEnumerable<T>異なるセマンティクスを持つことが重要です。

戻るIEnumerable<T>と、呼び出し元に、列挙できる (そしておそらくコレクションに変換できる) 何かのシーケンスが与えられます。ただし、シーケンスが実現されたという保証はありません。したがって、戻りIEnumerable<T>は遅延実行をサポートします。さらに、暗黙的に「読み取り専用」です。

戻るList<T>ということは、ここにコレクションがあることを意味します。メソッドが戻ると、すべての作業が完了しています。さらに、 のインターフェースList<T>により、呼び出し元はコレクションをすぐに変更することもできます。

于 2012-06-26T17:21:49.367 に答える