70

Array宣言されています:

public abstract class Array
    : ICloneable, IList, ICollection, IEnumerable {

なぜそうではないのだろうか。

public partial class Array<T>
    : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
  1. ジェネリック型として宣言された場合、どのような問題が発生しますか?

  2. それがジェネリック型だった場合でも、非ジェネリック型が必要ですか、それとも派生することができArray<T>ますか?そのような

    public partial class Array: Array<object> { 
    
4

7 に答える 7

149

歴史

配列がジェネリック型になった場合、どのような問題が発生しますか?

C#1.0に戻ると、彼らは主にJavaから配列の概念をコピーしました。ジェネリックスは当時存在していませんでしたが、作成者はそれらが賢いと考え、Java配列が持つ壊れた共変配列セマンティクスをコピーしました。これは、コンパイル時エラーなしでこのようなことを実行できることを意味します(ただし、代わりにランタイムエラー):

Mammoth[] mammoths = new Mammoth[10];
Animal[] animals = mammoths;            // Covariant conversion
animals[1] = new Giraffe();             // Run-time exception

C#2.0ではジェネリックが導入されましたが、共変/反変のジェネリック型はありませんでした。配列が汎用にされた場合、(壊れていたとしても)以前にできることであるにキャストMammoth[]することはできません。Animal[]したがって、配列を汎用にすると、多くのコードが壊れてしまいます。

C#4.0でのみ、インターフェイスの共変/反変ジェネリック型が導入されました。これにより、壊れた配列の共分散を完全に修正することが可能になりました。しかし、繰り返しになりますが、これは多くの既存のコードを壊していたでしょう。

Array<Mammoth> mammoths = new Array<Mammoth>(10);
Array<Animal> animals = mammoths;           // Not allowed.
IEnumerable<Animals> animals = mammoths;    // Covariant conversion

配列はジェネリックインターフェイスを実装します

配列が汎用およびインターフェースIList<T>を実装しないのはなぜですか?ICollection<T>IEnumerable<T>

ランタイムトリックのおかげで、すべてのアレイT[] 自動的に実装さIEnumerable<T>ICollection<T>ますIList<T>1Arrayクラスのドキュメントから:

一次元配列は、、、、IList<T>およびジェネリックインターフェイスICollection<T>を実装IEnumerable<T>します。実装は実行時に配列に提供されるため、ジェネリックインターフェイスはArrayクラスの宣言構文に表示されません。IReadOnlyList<T>IReadOnlyCollection<T>


配列によって実装されたインターフェイスのすべてのメンバーを使用できますか?

いいえ。ドキュメントは次のように続きます。

これらのインターフェイスの1つに配列をキャストするときに注意すべき重要な点は、要素を追加、挿入、または削除するメンバーがスローすることNotSupportedExceptionです。

ICollection<T>これは、(たとえば)Addメソッドがありますが、配列に何も追加できないためです。例外がスローされます。これは、実行時に例外がスローされる.NETFrameworkの初期の設計エラーのもう1つの例です。

ICollection<Mammoth> collection = new Mammoth[10];  // Cast to interface type
collection.Add(new Mammoth());                      // Run-time exception

また、ICollection<T>は共変ではないため(明らかな理由で)、これを行うことはできません。

ICollection<Mammoth> mammoths = new Array<Mammoth>(10);
ICollection<Animal> animals = mammoths;     // Not allowed

もちろん、現在、フード1の下の配列によっても実装されている共変IReadOnlyCollection<T>インターフェースがありますが、含まれているのは限られた用途にすぎません。Count


基本クラスArray

配列がジェネリックである場合でも、非ジェネリックArrayクラスが必要ですか?

初期の頃はそうしました。すべての配列は非ジェネリックIListを 実装し、基本クラスを介してインターフェイスしICollectionます 。これは、すべての配列に特定のメソッドとインターフェイスを与える唯一の合理的な方法であり、基本クラスの主な用途です。列挙型についても同じ選択肢があります。これらは値型ですが、;からメンバーを継承します。およびから継承するデリゲート。IEnumerableArrayArrayEnumMulticastDelegate

Arrayジェネリックがサポートされるようになったので、非ジェネリック基本クラスを削除できますか?

はい、すべての配列で共有されるメソッドとインターフェイスは、ジェネリッククラスが存在する場合は、ジェネリッククラスで定義できますArray<T>。そして、たとえば、ある種の安全性という追加の利点を使用するCopy<T>(T[] source, T[] destination)代わりに、書くことができます。Copy(Array source, Array destination)

ただし、オブジェクト指向プログラミングの観点からは、要素のタイプに関係なく、任意の配列Arrayを参照するために使用できる共通の非ジェネリック基本クラスがあると便利です。(一部のLINQメソッドでまだ使用されている)から継承する方法と同じです。IEnumerable<T>IEnumerable

Array基本クラスはから派生できArray<object>ますか?

いいえ、循環依存関係が作成されます:Array<T> : Array : Array<object> : Array : ...。また、これは、任意のオブジェクトを配列に格納できることを意味します(結局のところ、すべての配列は最終的にtypeから継承しますArray<object>)。


未来

Array<T>既存のコードにあまり影響を与えずに、新しいジェネリック配列タイプを追加できますか?

いいえ。構文を適合させることはできますが、既存の配列共分散を使用することはできませんでした。

配列は.NETの特殊なタイプです。共通中間言語での独自の指示もあります。.NETおよびC#の設計者がこの道を進むことを決定した場合、T[]構文のシンタックスシュガーを作成し(シンタックスシュガーの場合Array<T>と同様)、メモリ内で配列を連続して割り当てる特別な命令とサポートを引き続き使用できます。T?Nullable<T>

ただし、にキャストできないのと同様に、の配列をMammoth[]それらの基本タイプの1つにキャストする機能は失われます。しかし、配列の共分散はとにかく壊れており、より良い選択肢があります。Animal[]List<Mammoth>List<Animal>

配列共分散の代替案?

すべてのアレイはを実装しIList<T>ます。IList<T>インターフェイスが適切な共変インターフェイスになっている場合は、任意の配列Array<Mammoth>(またはそのことについては任意のリスト)をにキャストできますIList<Animal>。ただし、これにはIList<T>、基になる配列を変更する可能性のあるすべてのメソッドを削除するためにインターフェイスを書き直す必要があります。

interface IList<out T> : ICollection<T>
{
    T this[int index] { get; }
    int IndexOf(object value);
}

interface ICollection<out T> : IEnumerable<T>
{
    int Count { get; }
    bool Contains(object value);
}

T(これは共分散を壊すため、入力位置のパラメーターのタイプは使用できないことに注意してください。ただし、誤ったタイプのオブジェクトが渡されたときに戻るだけのとにobjectは十分です。これらのインターフェイスを実装するコレクションは、独自の汎用および。)ContainsIndexOffalseIndexOf(T value)Contains(T value)

次に、これを行うことができます:

Array<Mammoth> mammoths = new Array<Mammoth>(10);
IList<Animals> animals = mammoths;    // Covariant conversion

実行時は、配列の要素の値を設定するときに、割り当てられた値が配列の要素の実際の型と型互換であるかどうかをチェックする必要がないため、パフォーマンスがわずかに向上します。


私の刺し傷

Array<T>このようなタイプがC#と.NETで実装され、上記の実際の共変量IList<T>とインターフェイスと組み合わされた場合にどのように機能するかを調べましたICollection<T>。これは非常にうまく機能します。また、不変条件IMutableList<T>とインターフェースを追加して、新しいインターフェースとインターフェースに欠けIMutableCollection<T>ているミューテーションメソッドを提供しました。IList<T>ICollection<T>

その周りに簡単なコレクションライブラリを構築しました。BitBucketからソースコードとコンパイル済みバイナリをダウンロードするか、NuGetパッケージをインストールできます。

M42.Collections –組み込みの.NETコレクションクラスよりも多くの機能、機能、使いやすさを備えた特殊なコレクション。


1T[] ) .Net 4.5の配列は、その基本クラスを介して実装されますArray: 、、、、、、 ; ランタイムを通じてサイレントに:、、、、、および。ICloneableIListICollectionIEnumerableIStructuralComparableIStructuralEquatableIList<T>ICollection<T>IEnumerable<T>IReadOnlyList<T>IReadOnlyCollection<T>

于 2013-03-22T01:17:44.963 に答える
15

[更新、新しい洞察、今まで何かが欠けていると感じた]

以前の回答について:

  • 配列は、他の型と同様に共変です。「object[] foo = new string[5];」のようなものを実装できます。共分散があるので、それは理由ではありません。
  • 設計を再考しない理由はおそらく互換性ですが、これも正解ではないと主張します。

ただし、私が考えることができる他の理由は、配列がメモリ内の要素の線形セットの「基本型」であるためです。私は Array<T> の使用を考えていましたが、なぜ T がオブジェクトなのか、なぜこの「オブジェクト」が存在するのか疑問に思うかもしれません。このシナリオでは、T[] は、Array と共変である Array<T> の別の構文と考えられるものです。実際にはタイプが異なるため、2 つのケースは類似していると考えます。

基本的なオブジェクトと基本的な配列の両方が OO 言語の要件ではないことに注意してください。C++ はこの完璧な例です。これらの基本構造の基本型がないことの警告は、リフレクションを使用して配列またはオブジェクトを操作できないことです。オブジェクトの場合、「オブジェクト」を自然に感じさせる Foo のものを作成することに慣れています。実際には、配列基底クラスを持たないことで、Foo を実行することも同様に不可能になります。これはそれほど頻繁に使用されるわけではありませんが、パラダイムにとって同様に重要です。

したがって、C# に Array の基本型がなくても、豊富なランタイム型 (特にリフレクション) を使用することは、IMO では不可能です。

それでは、詳細を...

配列はどこで使用され、なぜ配列なのか

配列のように基本的なものの基本型を持つことは、多くのことに使用され、正当な理由があります。

  • 単純な配列

T[]そうですね、私たちは人々が を使用するのと同じように を使用することをすでに知っていましたList<T>。どちらも共通のインターフェイス セットを実装しています。正確には、 IList<T>ICollection<T>IEnumerable<T>IListICollectionおよびIEnumerableです。

これを知っていれば、簡単に配列を作成できます。私たちは皆、これが真実であることを知っており、それはエキサイティングではないので、先に進みます...

  • コレクションを作成します。

リストを掘り下げると、最終的には配列になります-正確には、T []配列です。

では、なぜですか?ポインター構造 (LinkedList) を使用することもできましたが、同じではありません。リストはメモリの連続ブロックであり、メモリの連続ブロックであることで速度が向上します。これには多くの理由がありますが、簡単に言えば、連続メモリを処理することは、メモリを処理する最速の方法です。CPU には、それを高速化するための命令さえあります。

注意深い読者は、これには配列が必要ではなく、IL が理解して処理できる型 'T' の要素の連続ブロックが必要であるという事実を指摘するかもしれません。つまり、IL で同じことを行うために使用できる別の型があることを確認する限り、ここで Array 型を取り除くことができます。

値型とクラス型があることに注意してください。可能な限り最高のパフォーマンスを維持するには、それらをそのままブロックに格納する必要があります...しかし、マーシャリングの場合、それは単に要件です。

  • マーシャリング。

マーシャリングでは、すべての言語が通信に同意する基本型を使用します。これらの基本型は、byte、int、float、pointer...、array などです。最も顕著なのは、次のような C/C++ での配列の使用方法です。

for (Foo *foo = beginArray; foo != endArray; ++foo) 
{
    // use *foo -> which is the element in the array of Foo
}

基本的に、これは配列の先頭にポインターを設定し、配列の末尾に到達するまで (sizeof(Foo) バイトで) ポインターをインクリメントします。要素は *foo で取得されます。これは、ポインタ 'foo' が指している要素を取得します。

値型と参照型があることにもう一度注意してください。オブジェクトとしてボックス化されたすべてを単純に格納する MyArray は本当に必要ありません。MyArray の実装は、非常にトリッキーになりました。

注意深い読者の中には、実際にはここで配列が必要ないという事実を指摘する人もいますが、これは事実です。Foo 型の要素の連続ブロックが必要です。値型の場合は、値型 (のバイト表現) としてブロックに格納する必要があります。

  • 多次元配列

もっと... 多次元性はどうですか?どうやら、ルールはそれほど白黒ではありません。なぜなら、突然すべての基本クラスがなくなったからです。

int[,] foo2 = new int[2, 3];
foreach (var type in foo2.GetType().GetInterfaces())
{
    Console.WriteLine("{0}", type.ToString());
}

強い型は窓の外に出たばかりで、コレクション型IListICollectionおよびになりIEnumerableます。ねえ、どうやってサイズを取得するのですか?Array 基本クラスを使用する場合は、次のように使用できます。

Array array = foo2;
Console.WriteLine("Length = {0},{1}", array.GetLength(0), array.GetLength(1));

... しかし、 のような代替案を見ると、IList同等のものはありません。これをどのように解決しますか?ここに導入する必要がありIList<int, int>ますか?基本型はただのint. どうIMultiDimentionalList<int>ですか?これを実行して、現在 Array にあるメソッドで埋めることができます。

  • 配列のサイズは固定です

配列を再割り当てするための特別な呼び出しがあることに気付きましたか? これはすべてメモリ管理に関係しています。配列は非常に低レベルであるため、成長や縮小が何であるかを理解していません。C では、これに 'malloc' と 'realloc' を使用します。また、直接割り当てるすべてのものに対して正確に固定サイズを使用することが重要である理由を理解するには、独自の 'malloc' と 'realloc' を実装する必要があります。

それを見ると、「固定」サイズで割り当てられるのは、配列、すべての基本的な値の型、ポインター、およびクラスの 2 つだけです。基本的な型の扱いが異なるように、明らかに配列の扱いも異なります。

型安全性に関する補足事項

そもそも、なぜこれらすべての「アクセス ポイント」インターフェイスが必要なのでしょうか。

どのような場合でも、ベスト プラクティスは、タイプ セーフなアクセス ポイントをユーザーに提供することです。これは、次のようなコードを比較することで説明できます。

array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't...

次のようにコーディングします。

((Array)someArray).GetLength(0); // do!

型安全性により、プログラミング時にずさんになることができます。正しく使用すると、実行時にエラーを検出するのではなく、作成したエラーをコンパイラが検出します。これがどれほど重要かはいくら強調してもしすぎることはありません。結局のところ、コードはテスト ケースでまったく呼び出されない可能性がありますが、コンパイラは常にコードを評価します。

すべてを一緒に入れて

では、まとめてみましょう。欲しいもの:

  • 厳密に型指定されたデータ ブロック
  • データが継続的に保存されている
  • IL のサポートにより、高速な処理を可能にするクールな CPU 命令を確実に使用できるようになります
  • すべての機能を公開する共通インターフェース
  • 型安全性
  • 多次元性
  • 値型を値型として保存したい
  • そして、そこにある他の言語と同じマーシャリング構造
  • メモリ割り当てが容易になる固定サイズ

これは、コレクションにとってかなりの低レベルの要件です...特定の方法でメモリを編成する必要があり、IL / CPUへの変換も必要です...基本的なタイプと見なされるのには十分な理由があると思います.

于 2013-01-15T19:15:31.247 に答える
12

互換性。配列は、ジェネリックがなかった時代にまでさかのぼる歴史的な型です。

Array今日では、 、次にArray<T>、そして特定のクラスを持つことが理にかなっているでしょう;)

于 2013-01-14T19:15:41.597 に答える
5

したがって、なぜそうではないのかを知りたいと思います。

その理由は、ジェネリックがC#の最初のバージョンに存在しなかったためです。

しかし、私は自分で何が問題になるのか理解できません。

Array問題は、クラスを使用する大量のコードを壊してしまうことです。C#は多重継承をサポートしていないため、このような行は

Array ary = Array.Copy(.....);
int[] values = (int[])ary;

壊れてしまいます。

MSがC#と.NETを最初からやり直す場合はArray、ジェネリッククラスを作成することに問題はないでしょうが、それは現実ではありません。

于 2013-01-14T19:31:32.743 に答える
3

人々が言及した他の問題に加えて、ジェネリックを追加しようとすると、Array<T>他のいくつかの問題が発生します。

  • ジェネリックが導入された瞬間から今日の共分散機能が存在していたとしても、それらは配列には十分ではありませんでした。a をソートするように設計されたルーチンは、配列の要素を type の要素にコピーしてからコピーして戻す必要がある場合でも、Car[]a をソートできます。typeから a への要素のコピーは、実際にはタイプ セーフではありませんが、便利です。並べ替えを可能にするような方法で共変配列の単一次元配列インターフェイスを定義することはできますが (たとえば、Swap(int firstIndex, int secondIndex) メソッドを含めることによって)、次のように柔軟なものを作成することは困難です。配列は。Buick[]CarCarBuick[]

  • Array<T>型は a に対してうまく機能するかもしれませんが、任意の数の添字に対して 、 、 などを含むファミリT[]を定義するジェネリック型システム内の手段はありません。T[]T[,]T[,,]T[,,,]

  • .net には、同じオブジェクトへの参照を保持する両方の変数を使用して、 type の変数を type の 1 つに、またはその逆にT1コピーできるように、2 つの型を同一と見なす必要があるという概念を表現する手段がありません。T2型を使用する人Array<T>はおそらく、 を期待するコードにインスタンスを渡し、T[]を使用するコードからインスタンスを受け入れたいと思うでしょうT[]。新しいスタイルを使用するコードとの間で古いスタイルの配列を渡すことができない場合、新しいスタイルの配列は機能よりも障害になります。

型システムをジンクスして、本来のArray<T>動作をする型を許可する方法があるかもしれませんが、そのような型は、他のジェネリック型とはまったく異なる多くの方法で動作し、目的の動作を実装する型が既に存在するためです。 (すなわちT[])、別のものを定義することでどのような利点が得られるかは明らかではありません。

于 2013-03-25T16:28:40.163 に答える
2

誰もが言うように、オリジナルArrayはv1で存在するようになったときにジェネリックがなかったため、ジェネリックではありません。以下の憶測...

「配列」をジェネリックにする(今では意味があります)には、次のいずれかを実行できます。

  1. 既存Arrayを維持し、汎用バージョンを追加します。これは素晴らしいことですが、「配列」のほとんどの使用法は時間の経過とともにそれを成長させることを含み、同じ概念のより良い実装がList<T>代わりに実装された理由である可能性が最も高いです。この時点で、「成長しない要素のシーケンシャルリスト」の汎用バージョンを追加することはあまり魅力的ではありません。

  2. 非ジェネリックを削除し、同じインターフェースを持つジェネリック実装にArray置き換えます。Array<T>ここで、既存のタイプではなく新しいタイプで動作するように、古いバージョンのコンパイル済みコードを作成する必要がありますArray。フレームワークコードがそのような移行をサポートすることは可能ですが(おそらく難しい)、他の人によって書かれたコードは常にたくさんあります。

    Array非常に基本的なタイプであるように、既存のコードのほとんどすべての部分(リフレクション付きのカスタムコードとネイティブコードおよびCOMによるマーシャリングを含む)がそれを使用します。その結果、バージョン間のわずかな非互換性(.NetFrameworkの1.x->2.x)の価格は非常に高くなります。

したがって、結果Arrayタイプは永遠にとどまるためにあります。これList<T>で、一般的に使用できるものがあります。

于 2013-03-22T02:37:17.703 に答える
0

多分私は何かが欠けているかもしれませんが、配列インスタンスがICollection、IEnumerableなどにキャストまたは使用されていない限り、Tの配列では何も得られません.

配列は高速で、既にタイプ セーフであり、ボックス化/ボックス化解除のオーバーヘッドが発生しません。

于 2013-03-26T23:28:38.197 に答える