リストが利用可能なときに配列を使用する理由はありますか?[閉まっている]


30

List<T>C#では、配列でできることはすべて実行できるように思われます。また、配列と同じくらいメモリとパフォーマンスが効率的であるようです。

それでは、なぜ配列を使用したいのでしょうか?

APIや他の外部制約(メイン関数など)で配列を使用する必要がある場合については明らかに質問していません...自分のコードで新しいデータ構造を作成することについてのみ質問しています。


56
実装するにはList<T>
ラチェットフリーク14

43
is also just as efficient in memory and performance as an array-ええと。その概念はどこから得たのですか?
オデッド

12
以下のような複数の寸法var test = new string[5,5];)
Knerd

7
一般的に使用されるbyte []配列もあります。
SBoss

11
特にC#については、Eric Lippertがこれについてblogs.msdn.com/b/ericlippert/archive/2008/09/22/…で書いています。
Mephy 14

回答:


26

仕事に行くときにトラックを運転しないのと同じ理由。機能を使用しないものは使用しません。

まず、配列はプリミティブな構造であるため、配列は確かにList <>よりも高速で効率的であるため、引数は正しくありません。配列はどこでも利用でき、さまざまな言語とプラットフォームを使用する開発者に知られています。

List <>の代わりに配列を使用する最も重要な理由は、データが固定長であることを意味するためです。そのデータコレクションにアイテムを追加または削除しない場合は、タイプがそれを反映していることを確認する必要があります。

もう1つは、新しいデータ構造を実装していて、それに関するいくつかの論文を読んだとしましょう。現在、特定のアルゴリズムを実装している間、他の誰かの汎用タイプの実装に常に頼ることはできません。.NETからMonoに変更され、フレームワークの異なるバージョン間でも変更されます。

また、フレームワーク依存型の代わりに配列を使用するコードを移植する方が簡単な場合があります。


4
配列List<T>を使用して実装されていることを考慮して、配列はリストよりもどのように「高速」ですか?事前に要素数を知っている場合(配列を使用するときに知っておく必要があります)、リストを初期化するときにもその知識を使用できます。
セオドロスチャツィジアンナキス14

1
@TheodorosChatzigiannakis配列を使用する場合、メモリを割り当て、単純に割り当てを行います。単純なmalloc()、オーバーヘッドなしです。List <>を使用している場合、アイテムを「追加」し、アイテムを追加する場合、List <>は境界チェックを行い、EnsureCapacityメソッドを呼び出します。つまり、新しいメモリを動的に割り当てる必要がない場合、Listのパフォーマンスは配列ほど良くありません。
メルトアカカヤ14

4
あなたが言っていることは正しいですが、それは我々が事前に要素数を知らないのどちらかということ(その場合には、配列はとにかく有用ではない)、または私たちがいることを前提としていない要素の数を知っておくが、我々が意図的に使用されていない操作を行いますリストの場合。私たちがした場合行う所望の容量を持つリストを初期化する要素の数を使用して、我々は(あなたがその容量に達するまで)、したがって、私たちは同じパフォーマンスコストを支払うだけで1つの配列の割り当てを取得します。他のすべての一般的な操作は同じです(検索、インデックスによる取得、インデックスによる割り当て)。ただし、追加はありません(配列にはまったくありません)。
セオドロスチャツィジアンナキス14

3
@TheodorosChatzigiannakis:配列リストよりも高速です。簡単なベンチマークを実行して確認してください。
Mehrdad 14

1
@TheodorosChatzigiannakis、はい、しかしまだ境界チェックがあります。
メルトアカカヤ14

18

もちろん、可変構造体のコレクションを管理するには配列が必要であり、それらがなければ何をするでしょうか。

struct EvilMutableStruct { public double X; } // don't do this

EvilMutableStruct[] myArray = new EvilMutableStruct[1];
myArray[0] = new EvilMutableStruct()
myArray[0].X = 1; // works, this modifies the original struct

List<EvilMutableStruct> myList = new List<EvilMutableStruct>();
myList.Add(new EvilMutableStruct());
myList[0].X = 1; // does not work, the List will return a *copy* of the struct

(可変構造体の配列が望ましい場合もありますが、通常、配列内の可変構造体と他のコレクションのこの異なる動作は、回避すべきエラーの原因です)


さらに深刻なのは、参照によって要素渡したい場合、配列が必要なことです。すなわち

Interlocked.Increment(ref myArray[i]);  // works
Interlocked.Increment(ref myList[i]);   // does not work, you can't pass a property by reference

これは、ロックフリーのスレッドセーフコードに役立ちます。


固定サイズのコレクションをデフォルト値で迅速かつ効率的に初期化するには、配列が必要です

double[] myArray = new double[1000]; // contains 1000 '0' values
                                     // without further initialisation

List<double> myList = new List<double>(1000) // internally contains 1000 '0' values, 
                                             // since List uses an array as backing storage, 
                                             // but you cannot access those
for (int i =0; i<1000; i++) myList.Add(0);   // slow and inelegant

(同じことを行うListのコンストラクターを実装することも可能です。c#がこの機能を提供していないだけです)


コレクションの一部を効率的にコピーする場合は配列が必要です

Array.Copy(array1, index1, array2, index2, length) // can't get any faster than this

double[,] array2d = new double[10,100];
double[] arraySerialized = new double[10*100];
Array.Copy(array2d, 0, arraySerialized, 0, arraySerialized.Length);
// even works for different dimensions

(これもListに実装できるものですが、この機能はc#には存在しません)


型が、ダクトテープ(ポイントの座標など)でつながれた関連するが独立した変数のグループを表す場合、露出フィールド構造体が最適な表現です。変数がそのようなグループの束を保持することになっている場合、その構造タイプの配列が最適な表現です。オブジェクトが必要な場所では可変構造体を使用するべきではありませんが、それは、ダクトテープで束ねられた変数が必要な場所でオブジェクトまたはオブジェクトのふりをするオブジェクトまたは物を使用する必要があるという意味ではありません。
supercat 14

パフォーマンスの最後のビットが必要で、ヒープの割り当てと参照に余裕がない場合など、可変構造が必要な場合があります。その場合、構造の配列が最善の策です。ここで良いエッセイ。しかし、クラスが「束になった変数」を表すべきではないことに同意しません。概念的には、構造体とクラスはほとんど同じですが、主な違いは実装の詳細です。
HugoRune 14

可変オブジェクト参照のコレクションを独立変数の独立グループのコレクションとして使用したい場合、各要素がユニバースのどこにも他の非一時参照が存在しないオブジェクトを識別するという不変条件を確立および維持する必要があります。それは確かに行うことができます(そしてしばしば行われます)が、その不変条件を支持するプログラマーを支援する言語やフレームワークには何もありません。対照的に、4つのインスタンスフィールドを持つ構造型の長さ100の配列は、400個の独立変数を自動的かつ永続的にカプセル化します。
supercat 14

これらのコメントは、この問題をさらに議論するのに間違った場所であり、独自の回答を追加したので、ここでこの情報を複製する必要はないと思われます。言うまでもなく、配列は可変構造体の管理に使用できます。特定のデータ制約を実施するためにこれらの構造体の実装動作を使用するかどうか、またいつ使用するかは、本当に私の答えの範囲を超えています。
HugoRune 14

15

それでは、なぜ配列を使用したいのでしょうか?

まれに、一定数の要素が必要なことがわかっているシナリオがあります。設計の観点から、これは避けるべきです。3つのものが必要な場合、ビジネスの性質上、次のリリースでは4つが必要になることが非常に多くあります。

それでも、このまれなシナリオが実際に発生する場合、配列を使用してその固定サイズの不変条件を強制すると便利です。固定サイズであるというシグナルを他のプログラマーに提供し、誰かが要素を追加または削除する誤用を防止するのに役立ちます-コードの他の部分の期待を破ります。


23
配列のサイズは、コードを記述する時点では明確に設定されていません。これは、ランタイム変数である場合が多く、多くの場合、ランタイム変数です。配列が作成された後、変更されることはありません。

11
固定数の要素を扱うことは珍しいことではありません。3Dポイントを使用している場合、将来どの時点でも5Dポイントを使用することはおそらくないでしょう。配列の大きな問題は、配列が可変であり、要素に便利なラベルが付けられていないことです。
ドーバル14

3
@JanDvorakリストは縮小または拡大する可能性があるため、固定数の要素を含むコンテキストでは意味がありません。
ドーバル14

9
めったに?誰もが非常に複雑な超構成可能なエンタープライズ怪物に取り組んでいるわけではありません。
whatsisname

3
固定数の要素を持つことは非常に一般的です。長さが定数によって定義されていることを確認してください。したがって、次のリリースで要素の数が3から4に増えると、定数を変更するだけで、これに依存するすべてが適応されます。
ハンス14

9

あなたの質問は実際に以前すでに答えられました

また、メモリやパフォーマンスにおいてもアレイと同じくらい効率的です。

そうではありません。私がリンクした質問から:

List/foreach:  3054ms (589725196)
Array/foreach: 1860ms (589725196)

特定の重要なケースでは、配列の速度は2倍です。私は、メモリ使用量も重要な違いがあると確信しています。

あなたの質問の主な前提はこのように破られているので、私はこれがあなたの質問に答えると仮定しています。これに加えて、Win32 API、GPUのシェーダー、またはその他の非DotNetライブラリによって配列が強制される場合があります。

DotNet内でも、一部のメソッドは配列(などString.Split)を消費したり返したりします。どの手段あなたが今、呼び出しのコストを食べる必要がありますどちらかToListToArrayすべての時間を、またはあなたはおそらくの貧しい下流のユーザーにこれを伝播させることによって、サイクルを続け、配列に適合して使用する必要がありますあなたのコード。

このトピックのスタックオーバーフローに関するその他の質問と回答:


興味深いことに、その答えは、昔ながらのインデックス付きループ(foreachではなくfor)を使用する場合、パフォーマンスの違いは最小限であると指摘しています。
user949300 14

3

他の回答に記載されている理由に加えて、配列リテラルは宣言するのに必要な文字が少なくなります。

var array = new [] { "A", "B", "C" };
var list = new List<string>() { "A", "B", "C" };

代わりに配列を使用Listすると、(1)IEnumerable<T>リテラルを渡す必要がある場合、または(2)のその他の機能がList重要ではなく、リストのようなものを使用する必要がある場合に、コードを少し短くして読みやすくしますリテラル。

私は時々単体テストでこれを行いました。


1
はい、追加するだけです。それはまた、あなたの作品は、合格する必要がありますのIList <T>
エスベンSkovがペダーセン

+1、多くの場合、コレクションにない同じタイプのいくつかのオブジェクトに対して何らかの操作があります。それは、簡単に書くことが短いと明確だforeach( var x in new []{ a, b, c ) ) DoStuff( x )new []{ a, b, c ).Select( ... )など
stijn

3

これは厳密にオブジェクト指向の観点からです。

配列だけを渡す理由は考えられませんが、おそらくクラス内部の配列表現がおそらく最良の選択である状況を見ることができます。

同様の特性を提供する他のオプションがありますが、処理順列、ネストされたforループ、マトリックス表現、ビットマップ、データインターリーブアルゴリズムを処理する問題の配列ほど直感的ではありません。

マトリックス数学に広範囲に依存している科学分野が相当数あります。(例えば、画像処理、データ誤り訂正、デジタル信号処理、一連の応用数学問題)。これらのフィールドのほとんどのアルゴリズムは、多次元配列/マトリックスの使用に関して記述されています。そのため、アルゴリズムのベースとなっている論文との直接的な結び付きを失うことを犠牲にして、アルゴリズムをより「ソフトウェア」にフレンドリーにするのではなく、定義されたアルゴリズムを実装する方が自然です。

私が言ったように、これらのケースではおそらくリストの使用を回避できますが、それはすでに複雑なアルゴリズムの上にさらに別の複雑さの層を追加します。


3

これは、実際にはリストを持つ他の言語(JavaやVisual Basicなど)にも当てはまります。メソッドがリストではなく配列を返すため、配列を使用する必要がある場合があります。

実際のプログラムでは、配列が頻繁に使用されるとは思いませんが、データが固定サイズであることがわかっていて、配列を使用することで得られる小さなパフォーマンスの向上が好きな場合があります。リストを返すメソッド、または多次元データ構造の必要性と同様に、マイクロ最適化は正当な理由です。


1
多くの言語には、リストコレクションと配列コレクションがありません。一方、C / C ++では、list<T>where vector<T>will work を使用することは悲惨な悪い考えです。
Gort the Robot

1
「メソッドが配列を返すため」-これはJavaの誤機能であり、プログラマは「Arrays.asList(x)」を使用してコードを作成する必要があります。T []は少なくともIterable <T>を実装する必要があります
kevin cline 14

4
@StevenBurnap-それは確かにCでは悲惨なほど悪いです。そのコードをコンパイルする方法がないからです。
ピートベッカー14


申し訳ありませんが、私はCハンドロールと同等のものを意味しましたlist<T>。基本的に、配列がより良い選択であったときにデフォルトでリストを使用するだけで、開発者によって引き起こされる多くのパフォーマンスの問題を見てきました。
ロボット

1

まあ、私は書いているゲームで配列の使用を見つけました。固定数のスロットを持つインベントリシステムの作成に使用しました。これにはいくつかの利点がありました。

  1. どのスロットがnullであるかを見て、ゲームオブジェクトに開いているインベントリスロットの数を正確に知っていました。
  2. 各アイテムがどのインデックスに含まれているかを正確に知っていました。
  3. まだ「型付き」配列(Item [] Inventory)であったため、「Item」型のオブジェクトを追加/削除できました。

インベントリのサイズを「増やす」必要がある場合、古いアイテムを新しいアレイに転送することでそれができると考えましたが、インベントリは画面スペースによって固定されており、動的に大きくする必要はありませんでした/より小さい、それは私がそれを使用していた目的のためにうまくいきました。


1

リストのすべての要素をトラバースする場合、いいえ、配列は不要です。「次」または任意の「置換なしの選択」で問題ありません。

ただし、アルゴリズムがコレクション内の要素へのランダムアクセスを必要とする場合は、はい、配列が必要です。

これは、「gotoが必要ですか?」にやや似ています。合理的な現代言語では、まったく必要ありません。しかし、ある時点で抽象化を剥がすと、実際に利用できるのはそれだけです。つまり、これらの抽象化を実装する唯一の方法は「不要な」機能を使用することです。(もちろん、アナロジーは完璧ではありません。配列はプログラミングの実践が貧弱だと言う人はいないと思います。それらは理解し、考えるのは簡単です)。


List <T>はランダムアクセスも提供します。確かに、配列で実装されていますが、ユーザーを煩わせる必要はありません。せいぜい、配列を使用する理由は1つだけですList<T>

@delnanそれが抽象化に関する私のポイントだと思います。効率の理由だけでなく、配列の観点から考えるほうが良い場合もあります。(もちろん、時にはそれがリンクリストを考えるために良い方法である、とずっといいそれよりも)それが実装されているか、リンクまたはその他気にすることなく(リストを考えること、および方法は、より良いだけのコレクション。
ミッチ

0

レガシー互換性。

すべてが個人的な経験を形成します:

レガシープログラマ-私の同僚はあらゆる場所でアレイを使用しており、30年以上にわたって行ってきました。

レガシーコード-foo(array bar [])list / vector / collection toarray関数を使用できることを確認しますが、追加機能を使用しない場合は、最初に配列を使用する方が簡単で、多くの場合、タイプ切り替えなしで読みやすくなります。

レガシーボス-私のボスは何年も前に管理職に就く前は優れたプログラマーでしたが、「アレイを使用している」ことで会議が終了し、コレクションが誰にとっても昼食にかかることを説明できると考えています。


3
20年の曲線と、使用しているもののデータ・タイプを知りたいボスの背後にある同僚が...私は私の仕事の可能性がどのくらいに悪い自分自身を思い出させるためにこのサイトを愛して
JoelFan

1
私たちが行っていることの約40%がKBで議論が行われる組み込み設計です。彼は時代遅れではないことを告白したいと思います。笑
Skeith 14

0

1)多次元バージョンのリストはありません。データに複数のディメンションがある場合、リストを使用するのは非常に非効率的です。

2)多数の小さなデータタイプ(たとえば、地形タイプの1バイトだけのマップ)を処理している場合、キャッシュによりパフォーマンスにかなりの違いが生じる可能性があります。配列バージョンは、読み込まれたメモリごとに複数のアイテムをロードし、リストバージョンは1つだけロードします。さらに、配列バージョンはキャッシュにリストバージョンの数倍のセルを保持します。データを繰り返し処理している場合、配列バージョンがキャッシュに収まるがリストバージョンが収まらない場合、これは大きな違いを生む可能性があります。

極端な場合は、Minecraftを検討してください。(ええ、C#では書かれていません。同じ理由が当てはまります。)



0

タイプTの100要素配列は、タイプTの100個の独立変数をカプセル化します。TがタイプQの可変パブリックフィールドとタイプRの1つを持つ値タイプである場合、配列の各要素は独立変数をカプセル化しますしたがって、配列全体では、タイプQの100個の独立変数とタイプRの100個の独立変数がカプセル化されます。これらの変数は、他に影響を与えることなく個別にアクセスできます。 配列以外のコレクションタイプでは、構造体のフィールドを独立変数として使用できません。

代わりにTがタイプQおよびRのパブリック可変フィールドを持つクラスタイプである場合、配列の各要素は、ユニバースの任意の場所でのインスタンスへの唯一の参照を保持し、配列Tの要素がまったくない場合外部参照が存在するオブジェクトを識別するように変更すると、配列はタイプQの100個の独立変数とタイプRの100個の独立変数を効果的にカプセル化します。他のコレクション型は、このような配列の動作を模倣できますが、配列の例は、タイプQの100個の変数とタイプRの100個の変数をカプセル化することです。変数の各ペアを独自のクラスオブジェクトにカプセル化することは、それを行う高価な方法です。さらに、配列または可変クラス型のコレクションを使用すると、配列要素で識別される変数が独立していない可能性があります

型が何らかのオブジェクトのように振る舞うことになっている場合、それはクラス型か、置換以外の変更の手段を提供しないプライベートフィールド構造のいずれかでなければなりません。しかし、型が独立した関連-はなく、変数の束のようにダクトテープで貼り合わせ動作するようになっている場合には、1型使用する必要がありますされて露出フィールドの構造体-変数の束がダクトテープで貼り合わせを。このような型の配列は、非常に効率的に機能し、非常にクリーンなセマンティクスを備えています。他のタイプを使用すると、セマンティクスが混乱したり、パフォーマンスが低下したり、またはその両方になります。


0

1つの重要な違いは、メモリの割り当てです。たとえば、リンクリストを走査すると、多くのキャッシュミスが発生し、パフォーマンスが低下する可能性がありますが、配列は特定のデータ型の複数のインスタンスを保持する連続したメモリチャンクを表し、順番に走査するとCPUにヒットする可能性が高くなりますキャッシュ。

もちろん、オブジェクト参照の配列は、キャッシュ参照の恩恵をあまり受けない場合があります。これは、逆参照によってメモリ内のどこにでも移動できるためです。

次に、配列を使用してリストを実装するArrayListなどのリスト実装があります。それらは便利なプリミティブです。


2
この質問List<T>では、リンクリストを使用して実装されるのではなく、配列を使用して実装される.Netタイプについて具体的に質問します(これは基本的ArrayList<T>にJava と同等です)。
svick 14

-2

選択するタイミングと選択するタイミングについて使用できるガイダンスを次に示しArrayますList

  • Arrayメソッドから戻るときに使用します。
  • List(メソッド内で)戻り値を構築するときに変数として使用します。次に.ToArray()、メソッドから戻るときに使用します。

一般に、Arrayコンシューマーがコレクションにアイテムを追加するつもりがない場合に使用します。List消費者がコレクションにアイテムを追加するときに使用します。

Array「静的」コレクションをList扱うためのものですが、「動的」コレクションを扱うためのものです。


2
提案するルールは任意であり、不必要なコピーを実行する非効率的なコードになる可能性があります。少なくとも、それを正当化する何らかの形が必要です。
ジュール14

@Jules開発者の経験は、マイクロ最適化よりもはるかに重要であることがわかりました。このレベルでパフォーマンスの問題はありません。
ダニエルリドストローム14

ただし、このルールによって開発者のエクスペリエンスがどのように改善されるかはわかりません。コードのクライアントが戻り値で何をしたいのかを推測する必要があり、間違えたときにコードを使用するのが不便になりますが、それに対する利点はまったくわかりません。パフォーマンスは二次的な関心事かもしれませんが、何も得られないルールに従うためにそれを犠牲にするつもりはありません。
ジュール14

@Julesそれはあなた自身の好みに帰着すると思います。私が使用することを好むような私は、定数として戻り値を処理することを好むArrayの代わりにList。あなたの考えを聞いてうれしいです!
ダニエルリドストローム14

UmmodifiableListsの使用を検討しましたか?
ジュール14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.