なぜIListまたはListを使用するのですか?


82

これに関する投稿がたくさんあることは知っていますが、IListのようなインターフェイスを渡して、具体的なリストの代わりにIListのようなインターフェイスを返す必要がある理由はまだ混乱しています。

これにより、後で実装を簡単に変更できるという投稿をたくさん読みましたが、それがどのように機能するかを完全には理解していません。

私がこの方法を持っているかどうかを言う

  public class SomeClass
    {
        public bool IsChecked { get; set; }
    }

 public void LogAllChecked(IList<SomeClass> someClasses)
    {
        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                // log 
            }
        }
    }

IListを使用することが将来どのように役立つかわかりません。

私がすでにメソッドに入っている場合はどうですか?まだIListを使用する必要がありますか?

public void LogAllChecked(IList<SomeClass> someClasses)
    {
        //why not List<string> myStrings = new List<string>()
        IList<string> myStrings = new List<string>();

        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                myStrings.Add(s.IsChecked.ToString());
            }
        }
    }

IListを使用すると何が得られますか?

public IList<int> onlySomeInts(IList<int> myInts)
    {
        IList<int> store = new List<int>();
        foreach (var i in myInts)
        {
            if (i % 2 == 0)
            {
                store.Add(i);
            }
        }

        return store;
    }

今はどう?変更する必要があるintのリストの新しい実装はありますか?

基本的に、IListを使用すると、Listをすべてに取り込むだけでなく、問題がどのように解決されるかを示す実際のコード例をいくつか見る必要があります。

私の読書から、私はただ物事をループしているので、IListの代わりにIEnumberableを使用できたと思います。

編集 だから私はこれを行う方法について私の方法のいくつかで遊んでいます。戻り値の型についてはまだわかりません(より具体的なものにするか、インターフェイスにする必要があるか)。

 public class CardFrmVm
    {
        public IList<TravelFeaturesVm> TravelFeaturesVm { get; set; }
        public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; }

        public CardFrmVm()
        {
            WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
            TravelFeaturesVm = new List<TravelFeaturesVm>();
        }
}

 public class WarrantyFeaturesVm : AvailableFeatureVm
    {
    }

 public class TravelFeaturesVm : AvailableFeatureVm
    {
    }

 public class AvailableFeatureVm
    {
        public Guid FeatureId { get; set; }
        public bool HasFeature { get; set; }
        public string Name { get; set; }
    }


        private IList<AvailableFeature> FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm)
        {
            List<AvailableFeature> availableFeatures = new List<AvailableFeature>();
            foreach (var f in avaliableFeaturesVm)
            {
                if (f.HasFeature)
                {
                                                    // nhibernate call to Load<>()
                    AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                    availableFeatures.Add(availableFeature);
                }
            }

            return availableFeatures;
        }

ここで、次のようなプロパティを持つドメインモデルにこれを追加するという単純な事実のためにIListを返します。

public virtual IList<AvailableFeature> AvailableFeatures { get; set; }

上記はIList自体です。これは、nhibernateで使用する標準のようです。そうでなければ、IEnumberableを返したかもしれませんが、よくわかりません。それでも、ユーザーが100%必要とするものを理解することはできません(コンクリートを返す方が有利な場合です)。

編集2

また、メソッドで参照渡しを実行したい場合はどうなるかを考えていました。

private void FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm, IList<AvailableFeature> toFill)
            {

                foreach (var f in avaliableFeaturesVm)
                {
                    if (f.HasFeature)
                    {
                                                        // nhibernate call to Load<>()
                        AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                        toFill.Add(availableFeature);
                    }
                }
            }

これで問題が発生しますか?配列(固定サイズ)を渡すことができなかったので?具体的なリストの方がいいのではないでしょうか。

回答:


156

ここには3つの質問があります。仮パラメーターにはどのタイプを使用する必要がありますか?ローカル変数には何を使用すればよいですか?戻り値の型には何を使用すればよいですか?

正式なパラメータ:

ここでの原則は、必要以上に要求しないことですIEnumerable<T>「このシーケンスの要素を最初から最後まで取得する必要があります」と通信します。IList<T>「このシーケンスの要素を任意の順序で取得および設定する必要があります」と通信します。List<T>「このシーケンスの要素を任意の順序で取得および設定する必要があり、リストのみを受け入れます。配列は受け入れません。」と通信します。

必要以上のことを求めることで、(1)発信者に不要な要求を満たすために不要な作業を行わせ、(2)読者に虚偽を伝えます。使用するものだけを尋ねてください。そうすれば、呼び出し元にシーケンスがある場合、要求を満たすために呼び出し元でToListを呼び出す必要はありません。

ローカル変数:

好きなものを使ってください。それはあなたの方法です。メソッドの内部実装の詳細を確認できるのはあなただけです。

戻り値の型:

以前と同じ原理、逆。発信者が必要とする最低限のものを提供します。呼び出し元がシーケンスを列挙する機能のみを必要とする場合は、それらにのみを与えますIEnumerable<T>


9
しかし、発信者が何を必要としているかをどうやって知るのですか。たとえば、戻り値の型の1つをIList <>に切り替えていた場合、おそらくそれらを列挙するだけで、IEnumberableを返すことができます。次に、ビュー(mvc)を調べたところ、forループを使用する必要があるため、実際にはcountメソッドが必要であることがわかりました。したがって、私自身のアプリケーションでは、実際に必要なものを過小評価していました。他の誰かが必要とするものと必要としないものをどのように予測しますか。
chobo2 2012年

@ chobo2:特定の例では、が。のCount場合、LINQはO(1)で機能します。もちろん、ループを使用することもできます。IEnumerableICollectionforeach
ブライアン

6
@ chobo2:では、発信者が必要とするメソッドをどのように予測しますか?それは最初に解決すべき問題のようです。おそらくどういうわけかあなたはそれらを呼び出すつもりの人々のためにどのような方法を書くべきかを知る方法を持っています。それらの人々に、メソッドに何を返したいかを尋ねます。あなたの質問は基本的に「どのソフトウェアを書くべきかをどうやって知るのか」です。顧客が解決しなければならない問題を知り、問題を解決するコードを書くことでわかります。
Eric Lippert

1
@ Eric-アイテムを追加するだけでよいICollectionの例を含めるために、正式なパラメーターの回答を更新する必要があります。また、戻り値の型の説明は、「呼び出し元に実行を許可する最低限のものだけを提供する」という行に沿って何かを言う必要があります。読み取り専用リストがIEnumerableなどのみを返す場合
Charles Lambert

4
@kvb:ほぼ。最初のシナリオを考えてみましょう。を実行することでアルゴリズムを改善できitems as IList<T>ます。null以外の場合は、改善されたコードを使用してください。できればまだ彼らは渡すものにクライアントの柔軟性を可能にしながら、あなたが、活用するこの方法。
エリックリペット

33

私が今まで見た中で最も実用的な理由は、CLRのジェフリー・リッチターがC#を介して与えたものです。

パターンは、引数に対して可能な限り基本的なクラスまたはインターフェイスを取得し、可能な限り最も具体的なクラスまたはインターフェイスを返すことです。し、戻り型にを返すことです。これにより、呼び出し元はメソッドに型を渡す際の柔軟性が最も高くなり、戻り値をキャスト/再利用する機会が最も多くなります。

たとえば、次の方法

public void PrintTypes(IEnumerable items) 
{ 
    foreach(var item in items) 
        Console.WriteLine(item.GetType().FullName); 
}

列挙可能なにキャストできる任意のタイプを渡してメソッドを呼び出すことができます。あなたがより具体的だった場合

public void PrintTypes(List items)

次に、配列があり、その型名をコンソールに出力したい場合は、最初に新しいリストを作成し、それに型を入力する必要があります。また、ジェネリック実装を使用した場合は、任意のオブジェクトで機能するメソッドしか使用できません。した場合は、特定のタイプのオブジェクトでのみ。

戻り値の型について話すとき、あなたがより具体的であるほど、より柔軟な発信者がそれを扱うことができます。

public List<string> GetNames()

この戻り値の型を使用して、名前を繰り返すことができます

foreach(var name in GetNames())

または、コレクションに直接インデックスを付けることができます

Console.WriteLine(GetNames()[0])

一方、あまり具体的でないタイプを取り戻した場合

public IEnumerable GetNames()

最初の値を取得するには、戻り値の型をマッサージする必要があります

Console.WriteLine(GetNames().OfType<string>().First());

10
このアドバイスは、戻り値の型の推奨におけるEricLippertの回答と矛盾することに注意してください。Jeffrey Richterのアプローチは、メソッドのコンシューマーに、返されたオブジェクトを好きなように使用するための最も柔軟性を与えます。一方、Ericのアプローチは、メソッドのパブリックサーフェスを変更せずに、メソッドのメンテナーに実装を変更するための最も柔軟性を与えます。私は内部コードについてジェフリーのアドバイスに従う傾向がありますが、公共図書館の場合は、おそらくエリックのアドバイスに従う傾向があります。
phoog 2012年

3
@phoog:エリックがどこから来たのかを考えると、彼が変更を壊すことについてもっと慎重であることは驚くことではありません。しかし、それは間違いなく有効なポイントです。

とてもタフ。両側に人がいるようです(裸分を返すかどうか)。不必要な変換を防ぐのに役立つパラメーターに対してそれを行う理由をもっと理解しています。戻り値の型をどうするかはまだわかりません。
chobo2 2012年

@ chobo2:実際、一般的ではなくより正確なものを指定した場合、将来的に戻り値の型を変更する必要があるかどうかを検討する必要があります。メソッドを検討できる場合は、戻りコレクションのタイプを変更しない可能性が高いと判断してください。より正確なタイプを返す方が安全である可能性があります。確信が持てない場合、または将来変更した場合に他の人のコードが破損することを恐れている場合は、より一般的にしてください。

1
+1雑学クイズ:この回答は、ポステルの法則のCLR固有の優れた例です。:)
ダンJ

13

IEnumerable<T>コレクションを反復処理できます。ICollection<T>これに基づいて構築されており、アイテムの追加と削除も可能です。IList<T>また、特定のインデックスでそれらにアクセスして変更することもできます。コンシューマーが使用することを期待するものを公開することで、実装を自由に変更できます。List<T>たまたまこれら3つのインターフェースすべてを実装しています。

プロパティをとして公開する場合、List<T>またはIList<T>消費者に提供してもらいたいのは、コレクションを反復処理する機能だけです。その後、リストを変更できるという事実に依存するようになる可能性があります。その後、実際のデータストアをaList<T>からaに変換しDictionary<T,U>、辞書キーをプロパティの実際の値として公開することにした場合(以前はこれを正確に行う必要がありました)。そうすると、自分の変更がクラス内に反映されることを期待するようになった消費者は、その機能を失います。それは大きな問題です!をList<T>として公開IEnumerable<T>すると、コレクションが外部から変更されていないことを快適に予測できます。それが露出の力の一つですList<T>、上記のインターフェイスのいずれかとして。

このレベルの抽象化は、メソッドパラメータに属する場合は逆方向に進みます。リストを受け入れるメソッドに渡すと、リストIEnumerable<T>が変更されないことを確認できます。あなたがメソッドを実装している人であり、あなたが受け入れると言うときIEnumerable<T>する必要があるのはそのリストを繰り返すことだけなので、あなたが。次に、メソッドを呼び出す人は、列挙可能な任意のデータ型でメソッドを自由に呼び出すことができます。これにより、コードを予期しない、しかし完全に有効な方法で使用できます。

このことから、メソッドの実装は、必要に応じてローカル変数を表すことができます。実装の詳細は公開されていません。コードを呼び出す人に影響を与えることなく、コードをより良いものに自由に変更できます。

未来を予測することはできません。プロパティのタイプが常に有益であると仮定すると、List<T>コードの予期しない期待に適応する能力がすぐに制限されます。はい、そのデータ型をから変更することはできませList<T>んが、必要に応じて変更することはできます。あなたのコードはそれの準備ができています。


9

簡潔な答え:

使用するインターフェースの具体的な実装に関係なく、コードがそれをサポートするように、インターフェースを渡します。

listの具体的な実装を使用する場合、同じリストの別の実装はコードでサポートされません。

継承とポリモーフィズムについて少し読んでください。


8

次に例を示します。あるプロジェクトでリストが非常に大きくなり、その結果、大きなオブジェクトヒープが断片化してパフォーマンスが低下していました。ListをLinkedListに置き換えました。LinkedListには配列が含まれていないため、突然、ラージオブジェクトヒープをほとんど使用できなくなりました。

IEnumerable<T>とにかく、ほとんどの場合、リストをとして使用したので、それ以上の変更は必要ありませんでした。(もちろん、参照を列挙するだけの場合は、参照をIEnumerableとして宣言することをお勧めします。)いくつかの場所で、リストインデクサーが必要だったため、非効率的に記述しました。IList<T>、リンクリストの周りにラッパーを作成しました。リストインデクサーが必要になることはめったにないので、非効率性は問題ではありませんでした。もしそうなら、おそらく十分に小さい配列のコレクションとして、IListの他の実装を提供することができたでしょう。それは、大きなオブジェクトを避けながら、より効率的にインデックスを付けることができたでしょう。

結局、何らかの理由で実装を置き換える必要があるかもしれません。パフォーマンスは1つの可能性にすぎません。理由に関係なく、可能な限り最小の派生型を使用すると、オブジェクトの特定の実行時型を変更するときにコードを変更する必要性が少なくなります。


3

メソッド内ではvarIListまたはの代わりに、を使用する必要がありますList。データソースが代わりにメソッドからのものに変更された場合、onlySomeIntsメソッドは存続します。

パラメータとしてIList代わりに使用する理由Listは、多くのものが実装されているためですIList(2つの例としてListと[])が、実装されてListいるのは1つだけです。インターフェイスにコーディングする方が柔軟性があります。

値を列挙するだけの場合は、を使用する必要がありますIEnumerable。複数の値を保持できるすべてのタイプのデータ型は、実装IEnumerable(またはすべき)であり、メソッドを非常に柔軟にします。


13
彼が「var」を使うべきだと言うのは完全に間違っています。彼がそこで何を使用するかは問題ではありません-これはスタイルの問題です。メソッドのシグネチャには影響せず、コンパイル時に設定されます。代わりに、IList foo = newListのようにローカルを宣言することについての彼の混乱を乗り越えるのを手伝うべきです-これは彼の混乱が明らかにあるところです。
x0n 2012年

つまり、基本的に、配列で送信したい場合は、最初に.ToListを実行する必要がないという事実のために、IListを取り込むと言っていますか?返品してみませんか?varsを使用するときに「メソッドは存続する」とはどういう意味かわかりませんか?もう少し手間がかかることは承知していますが、それを新しいタイプに変更する必要はありませんか?では、IList <int>からIList <String>になりますか?
chobo2 2012年

確かに、「use var」のポイントは、メソッド自体の内部でそれを気にせず、消費者にどのように見えるかに焦点を当てることを提案するものでした。
ブライアンベッチャー2012年

私は@ x0nに同意します:varはひどく使いすぎており、何も明確にするのに役立ちません。
そのチャックガイ

1

Listの代わりにIListを使用すると、単体テストの作成が大幅に簡単になります。これにより、「モック」ライブラリを使用してデータを渡したり返したりすることができます。

インターフェイスを使用するもう1つの一般的な理由は、オブジェクトのユーザーに必要な最小限の知識を公開することです。

IListを実装するデータオブジェクトがある(考案された)ケースを考えてみましょう。

public class MyDataObject : IList<int>
{
    public void Method1()
    {
       ...
    }
    // etc
}

上記の関数は、リストを反復処理できることだけを考慮しています。理想的には、誰がそのリストを実装するのか、またはどのように実装するのかを知る必要はありません。

あなたの例では、あなたが思ったようにIEnumerableがより良い選択です。


1

コード間の依存関係を可能な限り減らすことは常に良い考えです。

これを念頭に置いて、可能な限り少ない数の外部依存関係を持つ型を渡し、同じものを返すことが最も理にかなっています。ただし、これは、メソッドとそのシグネチャの可視性によって異なる場合があります。

メソッドがインターフェースの一部を形成する場合、そのインターフェースで使用可能なタイプを使用してメソッドを定義する必要があります。具象型はおそらくインターフェースで使用できないため、非具象型を返す必要があります。たとえば、フレームワークを作成する場合は、これを実行する必要があります。

ただし、フレームワークを作成していない場合は、可能な限り弱い型(つまり、基本クラス、インターフェイス、さらにはデリゲート)でパラメーターを渡し、具象型を返す方が有利な場合があります。これにより、呼び出し元は、インターフェースとしてキャストされている場合でも、返されたオブジェクトを可能な限り処理することができます。ただし、返されるオブジェクトタイプを変更すると呼び出し元のコードが破損する可能性があるため、これによりメソッドがより脆弱になります。しかし実際には、それは一般的に大きな問題ではありません。


1

呼び出し元がさまざまな具象型を引数として送信できるため、メソッドのパラメーターとしてInterfaceを受け入れます。サンプルメソッドLogAllCheckedを考えると、パラメーターsomeClassesはさまざまなタイプである可能性があり、メソッドを作成する人にとっては、すべてが同等である可能性があります(つまり、パラメーターのタイプに関係なく、まったく同じコードを作成します)。しかし、電話をかける人のために、メソッドを、大きな違いが生じる可能性があります。配列があり、リストを要求している場合、メソッドを呼び出すたびに配列をリストまたはvvに変更する必要があり、無駄になります。プログラマーとパフォーマンスPOVの両方からの時間。

インターフェースを返すか具体的な型を返すかは、作成したオブジェクトで呼び出し元に何をさせたいかによって異なります。これはAPI設計の決定であり、厳格なルールはありません。オブジェクトを最大限に活用する能力と、オブジェクトの機能の一部を簡単に利用できる能力(そしてもちろん、オブジェクトを最大限に活用したいかどうか)を比較検討する必要があります。たとえば、IEnumerableを返す場合は、反復に制限します。オブジェクトにアイテムを追加したり、オブジェクトからアイテムを削除したりすることはできず、オブジェクトに対してのみ機能します。クラスの外部にコレクションを公開する必要があるが、呼び出し元にコレクションを変更させたくない場合、これはそれを行う1つの方法です。一方、空のコレクションを返す場合は、それらにデータを入力することを期待/希望します。


0

これが、この.NET4.5以降の世界での私の答えです。

使用のIList <T>IReadonlyList <T>
              代わりのリスト<T> 、のでReadonlyList <T>は存在しません。

IList <T>IReadonlyList <T>と非常に一貫しているように見えます

  • 使用のIEnumerable <T>最小露光(プロパティ)又は要件(パラメータ)のための場合はforeachのは、それを使用する唯一の方法です。
  • Count[]も公開/使用する必要がある場合は、IReadonlyList <T>を使用しますインデクサー。
  • 呼び出し元が要素を追加/更新/削除することも許可する場合は、IList <T>を使用します

なぜならリスト<T>を実装IReadonlyList <T>、それが明示的なキャストを必要としません。

クラスの例:

// manipulate the list within the class
private List<int> _numbers;

// callers can add/update/remove elements, but cannot reassign a new list to this property
public IList<int> Numbers { get { return _numbers; } }

// callers can use: .Count and .ReadonlyNumbers[idx], but cannot add/update/remove elements
public IReadOnlyList<int> ReadonlyNumbers { get { return _numbers; } }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.