関数から配列またはコレクションを返す必要がありますか?
-
03-07-2019 - |
質問
関数から同じタイプの複数のオブジェクトを返す場合、推奨されるコンテナタイプは何ですか?
単純な配列(MyType []など)を返すのは良い習慣に反しますか、それとも汎用コレクション(ICollection <!> lt; MyType <!> gt;など)でラップする必要がありますか?
ありがとう!
解決
Eric Lippertには記事があります記事全体を読むのが面倒な場合、答えは次のとおりです。インターフェースを返す。
他のヒント
IEnumerable<T>
を使用してyield return
を返します。
IList<T>
を返します。これは、関数のコンシューマーに最大の柔軟性を与えるためです。関数のコンシューマーがシーケンスを列挙するだけでよい場合はそうすることができますが、リストとしてシーケンスを使用する場合は同様に行うことができます。
一般的な経験則では、最も制限の少ないタイプをパラメーターとして受け入れ、できる限りリッチなタイプを返します。もちろん、これはバランスをとる行為であり、特定のインターフェイスや実装に縛られたくはありません(ただし、常にインターフェイスを使用するようにしてください)。
これは、API開発者がとることができる最も控えめなアプローチです。関数の消費者が送信したものをどのように使用するかを決定するのはあなた次第ではありません。そのため、この場合、最大の柔軟性を与えるためにIEnumerable<T>
を返します。また、これと同じ理由で、消費者がどのタイプのパラメーターを送信するかを知ることは決してありません。パラメーターとして送信されたシーケンスを反復するだけでよい場合は、パラメーターをList<T>
ではなく<=>にします。
編集(一酸化):質問が閉じられるようには見えないので、これに関する他の質問からのリンクを追加したいだけです:なぜアレイが有害なのか
List<T>
ではない理由
他の人が言及したEric Lippertの投稿から、これを強調するつもりだと思いました:
シーケンスが必要な場合I <!>#8217;
IEnumerable<T>
、マッピングが必要な場合 連続した数字からデータまでI <!>#8217; ll マッピングが必要な場合は、Dictionary<K,V>
を使用します 任意のデータにわたってI <!>#8217; llを使用しますHashSet<T>
、セットが必要な場合I <!>#8217; ll <=>を使用します。私は単に<!>#8217; tは必要ありません なんでも配列 それらを使用します。彼らは<!>#8217; t問題を解決しません 私の他のツールよりも優れている
返されるコレクションが読み取り専用の場合、コレクション内の要素を変更したくない場合は、IEnumerable<T>
を使用します。これは、(少なくとも列挙自体の観点から)不変要素の読み取り専用シーケンスの最も基本的な表現です。
変更可能な自己完結型のコレクションにする場合は、ICollection<T>
またはIList<T>
を使用します。
たとえば、特定のファイルセットの検索結果を返す場合は、IEnumerable<FileInfo>
を返します。
ただし、ディレクトリ内のファイルを公開したい場合は、コレクションの内容を変更する可能性があるため、IList/ICollection<FileInfo>
を公開します。
引用されたことを聞いたことのない良いアドバイスは次のとおりです。
受け入れるものは寛大に、提供するものは正確にします。
APIの設計に関しては、具体的な型ではなく、インターフェイスを返すことをお勧めします。
サンプルのメソッドを使用して、次のように書き換えます。
public IList<object> Foo()
{
List<object> retList = new List<object>();
// Blah, blah, [snip]
return retList;
}
重要な点は、内部実装の選択(リストを使用すること)が呼び出し元に明らかにされていないことですが、適切なインターフェイスを返していることです。
フレームワーク開発に関するMicrosoft独自のガイドラインでは、特定の型を返さず、インターフェイスを優先するよう推奨しています。 (申し訳ありませんが、このリンクは見つかりませんでした)
同様に、パラメータはできるだけ一般的なものにする必要があります-配列を受け入れる代わりに、適切な型のIEnumerableを受け入れます。これは、配列やリスト、その他の便利なタイプと互換性があります。
サンプルメソッドを再度使用する:
public IList<object> Foo(IEnumerable<object> bar)
{
List<object> retList = new List<object>();
// Blah, blah, [snip]
return retList;
}
return ICollection<type>
一般的な戻り値型の利点は、使用するコードを変更せずに基礎となる実装を変更できることです。特定の型を返すことの利点は、より多くの型固有のメソッドを使用できることです。
常に、呼び出し側に最大限の機能を提供するインターフェイスタイプを返します。したがって、あなたの場合はICollection<YourType>
を使用する必要があります。
興味深いのは、BCL開発者が実際に.NETフレームワークのどこかでこれを間違えたことです-このストーリーに関するEric Lippertのブログ投稿。
IList<MyType>
ではない理由
いつかList<MyType>
を返す可能性を排除することなく、配列の特徴である直接インデックス付けをサポートします。この機能を抑制したい場合は、おそらくIEnumerable<MyType>
を返します。
返されるコレクションをどうするかによって異なります。単に反復している場合、またはユーザーに反復のみを許可する場合は、@ Danielに同意し、IEnumerable<T>
を返します。ただし、実際にリストベースの操作を許可する場合は、IList<T>
を返します。
ジェネリックを使用します。他のコレクションクラスとの相互運用がより簡単になり、型システムは潜在的なエラーをより支援できます。
配列を返す古いスタイルは、ジェネリックの前の松葉杖でした。
コードをより読みやすく、保守しやすく、あなたにとって簡単にするもの。 ほとんどの場合、simpler == betterの単純な配列を使用していました。 正しい答えを出すには、コンテキストを実際に見る必要があります。
IEnumerableを他のものよりも優先することには大きな利点があります。これにより、実装の柔軟性が最大になり、遅延実装にyield return
またはLinq演算子を使用できるようになります。
呼び出し元が代わりにList<T>
を必要とする場合、返されたものに対してToList()
を呼び出すだけでよく、全体的なパフォーマンスはメソッドから新しい<=>を作成して返した場合とほぼ同じです。
配列は有害ですが、ICollection<T>
も有害です。
ReadOnlyCollection<T>
は、オブジェクトが不変であることを保証できません。
私の推奨事項は、返されるオブジェクトを<=>
でラップすることです。