web-dev-qa-db-ja.com

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

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

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

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

30
JoelFan

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

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

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

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

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

27
Mert Akcakaya

もちろん、mutable structsのコレクションを管理するための配列が必要です。もちろん、これらがなければ何ができるでしょうか。

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

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


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

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#には存在しません)

19
HugoRune

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

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

それでも、このまれなシナリオが実際に発生する場合は、配列を使用して固定サイズの不変条件を適用すると便利です。これは、固定サイズであることを他のプログラマーに知らせ、誰かが要素を追加または削除することによる誤用を防ぎ、コードの他の場所での期待を破ります。

15
Telastyn

あなたの質問は実際に すでに回答済み です。

また、メモリとパフォーマンスも配列と同じくらい効率的です。

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

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

配列は、特定の重要な場合に2倍高速です。メモリ使用量も重要な違いがあると確信しています。

このようにあなたの質問の大前提は打ち負かされているので、これがあなたの質問に答えると思います。これに加えて、Win32 API、GPUのシェーダー、またはその他の非ドットネットライブラリによって配列が強制される場合があります。

DotNet内でも、一部のメソッドは配列を消費または返す、あるいはその両方を行います(String.Splitなど)。つまり、ToListToArrayを呼び出すコストを常に費やさなければならないか、配列を適合させて使用し、おそらくこれをyourコード。

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

10
Superbest

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

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

3
Dylan Meeus

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

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

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

私はこれを時々ユニットテストで行いました。

3
ikh

これは厳密にOOの観点からです。

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

同様の特性を提供する他のオプションもありますが、入れ子のforループ、行列表現、ビットマップ、およびデータインターリーブアルゴリズムを処理する問題を処理するための配列ほど直感的に見えるものはありません。

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

私が言ったように、これらの場合、おそらくリストを使用することでうまくいくかもしれませんが、それはすでに複雑なアルゴリズムの上にさらに別の複雑さの層を追加します。

3
Dunk

まあ、私が書いているゲームで配列の使用法を見つけました。一定数のスロットを持つ在庫システムを作成するために使用しました。これにはいくつかの利点がありました:

  1. どのスロットがnullであるかを調べて確認することで、ゲームオブジェクトが開いているインベントリスロットの数を正確に把握しました。
  2. 各項目がどのインデックスにあるかを正確に知っていました。
  3. それはまだ「型付き」配列(Item [] Inventory)だったので、「Item」型のオブジェクトを追加/削除できました。

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

1
m t

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

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

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

1
Mitch

レガシー互換性。

すべての個人的な経験:

レガシープログラマー-私の同僚はあらゆる場所で配列を使用しており、30年以上の実績があります。幸運なことに、あなたの新しいアイデアで心を変えてください。

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

レガシーボス-私の上司は、何年も前に管理職に就く前は優れたプログラマーでしたが、「配列を使用していた」ことで会議が終了し、コレクションの内容を説明すると全員に昼食がかかる可能性があることを説明し、最新の状態であると考えています。

0
Skeith

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

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

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

0
Loren Pechtel

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

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

型がある種のオブジェクトのように振る舞うことになっている場合、それはクラス型か、置換以外の変更の手段を提供しないプライベートフィールド構造である必要があります。ただし、タイプがダクトテープと一緒にスタックされた、関連しているが独立した変数の束のように動作すると想定される場合は、タイプを使用する必要がありますisダクトテープと一緒にスタックされた変数の束-公開フィールド構造体。このようなタイプの配列は、操作が非常に効率的で、セマンティクスが非常に明確です。他のタイプを使用すると、セマンティクスが混乱するか、パフォーマンスが低下するか、またはその両方が発生します。

0
supercat

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

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

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

0
Rory Hunter