Array
が宣言されています:
public abstract class Array
: ICloneable, IList, ICollection, IEnumerable {
私はなぜそうではないのだろうと思っています:
public partial class Array<T>
: ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
ジェネリック型として宣言された場合、問題は何になりますか?
ジェネリック型の場合、非ジェネリック型がまだ必要ですか、それともArray<T>
から派生できますか?といった
public partial class Array: Array<object> {
配列がジェネリック型になった場合、どのような問題が発生しますか?
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[]
にキャストできませんでした。したがって、配列を汎用化すると、コードが壊れてしまいますa lot。
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[]
doesランタイムトリックのおかげで、IEnumerable<T>
、ICollection<T>
、およびIList<T>
が自動的に実装されます。1 Array
クラスのドキュメント から:
1次元配列は、
IList<T>
、ICollection<T>
、IEnumerable<T>
、IReadOnlyList<T>
、およびIReadOnlyCollection<T>
ジェネリックインターフェイスを実装します。実装は実行時に配列に提供されるため、汎用インターフェイスはArrayクラスの宣言構文に表示されません。
配列で実装されたインターフェイスのすべてのメンバーを使用できますか?
いいえ。ドキュメントは次のコメントに続きます。
これらのインターフェイスの1つに配列をキャストするときに注意すべき重要なことは、要素を追加、挿入、または削除するメンバーが
NotSupportedException
をスローすることです。
(たとえば)ICollection<T>
にはAdd
メソッドがありますが、配列には何も追加できないためです。例外をスローします。これは、.NET Frameworkの初期設計エラーの別の例であり、実行時に例外がスローされます。
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
もちろん、現在、共変 IReadOnlyCollection<T>
interface があります。これは、内部の配列によっても実装されます。1、ただしCount
のみが含まれているため、使用が制限されています。
Array
配列がジェネリックである場合、非ジェネリック
Array
クラスが必要ですか?
初期の頃はそうでした。すべての配列は、非汎用 IList
、 ICollection
および IEnumerable
インターフェイスを実装します基本クラスArray
。これは、すべての配列に特定のメソッドとインターフェイスを提供する唯一の合理的な方法であり、Array
基本クラスの主な用途です。列挙型にも同じ選択肢があります。それらは値型ですが、Enum
からメンバーを継承します。 MulticastDelegate
を継承するデリゲート。
ジェネリックがサポートされているので、非ジェネリックベースクラス
Array
を削除できますか?
はい、すべての配列で共有されているメソッドとインターフェイスは、汎用Array<T>
クラスが存在する場合に定義できます。そして、例えば、Copy<T>(T[] source, T[] destination)
の代わりにCopy(Array source, Array destination)
を書くことで、何らかのタイプセーフティの利点を追加できます。
ただし、オブジェクト指向プログラミングの観点からは、タイプに関係なくany配列を参照するために使用できる一般的な非ジェネリックベースクラスArray
があると便利です。その要素。 IEnumerable<T>
がIEnumerable
(一部のLINQメソッドでまだ使用されている)から継承する方法と同じです。
Array
基本クラスはArray<object>
から派生できますか?
いいえ、それは循環依存関係を作成します:Array<T> : Array : Array<object> : Array : ...
。また、これはanyオブジェクトを配列に格納できることを意味します(結局、すべての配列は最終的に_Array<object>
型から継承します)。
既存のコードにあまり影響を与えずに、新しい汎用配列型
Array<T>
を追加できますか?
いいえ。構文を適合させることはできましたが、既存の配列共分散は使用できませんでした。
配列は、.NETの特別な型です。 Common Intermediate Languageでの独自の指示もあります。 .NETとC#のデザイナーがこの道を進むことに決めた場合、T[]
のArray<T>
構文構文シュガーを(T?
がNullable<T>
の構文シュガーと同じように)作成し、特別な命令と配列を連続して割り当てるサポートを使用できますメモリ。
ただし、Mammoth[]
の配列をその基本型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
とContains
には、IndexOf
で十分です。不正な型のオブジェクトが渡されたときにfalse
を返すだけです。これらのインターフェイスを実装するコレクションは、独自の汎用IndexOf(T value)
およびContains(T value)
を提供できます。)
次に、これを行うことができます:
Array<Mammoth> mammoths = new Array<Mammoth>(10);
IList<Animals> animals = mammoths; // Covariant conversion
ランタイムは、配列の要素の値を設定するときに、割り当てられた値が配列要素の実際の型と型互換性があるかどうかをチェックする必要がないため、わずかなパフォーマンスの改善さえあります。
上記の実際の共変のArray<T>
およびIList<T>
インターフェイスと組み合わせて、C#および.NETで実装された場合に、そのようなICollection<T>
型がどのように機能するかを確認しました。また、不変のIMutableList<T>
およびIMutableCollection<T>
インターフェイスを追加して、新しいIList<T>
およびICollection<T>
インターフェイスにない突然変異メソッドを提供しました。
その周りにシンプルなコレクションライブラリを構築しました。 BitBucketからソースコードとコンパイル済みバイナリをダウンロード にするか、NuGetパッケージをインストールします。
M42.Collections –組み込みの.NETコレクションよりも多くの機能、機能、使いやすさを備えた専用コレクションクラス。
1).Net 4.5のT[]
配列は、その基本クラスArray
を介して実装されます: ICloneable
、 IList
、- ICollection
、 IEnumerable
、 IStructuralComparable
、 IStructuralEquatable
;そして、ランタイムを通して静かに: IList<T>
、 ICollection<T>
、 IEnumerable<T>
、 IReadOnlyList<T>
、および IReadOnlyCollection<T>
=。
[更新、新しい洞察、今まで何かが足りないと感じていた]
以前の回答について:
しかし、私が考えることができるもう1つの理由は、配列がメモリ内の要素の線形セットの「基本型」であるためです。私はArray <T>の使用について考えてきましたが、Tがオブジェクトである理由と、この「オブジェクト」が存在する理由を疑問に思うかもしれません。このシナリオでは、T []はArrayと共変であるArray <T>の別の構文と考えるものです。実際にはタイプが異なるため、2つのケースは似ていると考えます。
基本オブジェクトと基本配列の両方がOO言語の要件ではないことに注意してください。C++はこのための完璧な例です。これらの基本構成に対して基本型がないという警告はありませんリフレクションを使用して配列またはオブジェクトを操作できます。オブジェクトの場合、「オブジェクト」を自然に感じるFooのオブジェクトを作成することに慣れています。実際には、配列の基本クラスがないと、Foo頻繁に使用されますが、パラダイムにとっても同様に重要です。
したがって、Arrayベース型なしでC#を使用し、豊富なランタイム型(特にリフレクション)を使用することはできません。
詳細については...
使用される配列はどこで、なぜ配列なのか
配列と同じくらい基本的なものの基本型を持つことは、多くのことと正当な理由で使用されます:
まあ、人々はT[]
を使用するのと同じように、List<T>
を使用することを既に知っていました。正確には、両方のインターフェイスの共通セットを実装します:IList<T>
、ICollection<T>
、IEnumerable<T>
、IList
、ICollection
および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());
}
強い型はウィンドウの外に出たばかりで、コレクション型IList
、ICollection
、および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」を実装して、all直接割り当てるもの。
見てみると、「固定」サイズで割り当てられるものは、配列、すべての基本的な値の型、ポインター、クラスの2つだけです。どうやら、基本型を異なる方法で処理するように、配列を異なる方法で処理しているようです。
型安全性に関する補足
そもそもなぜこれらすべての「アクセスポイント」インターフェースが必要なのでしょうか?
すべての場合のベストプラクティスは、ユーザーにタイプセーフなアクセスポイントを提供することです。これは、次のようなコードを比較することで説明できます。
array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't...
このようなコードに:
((Array)someArray).GetLength(0); // do!
タイプセーフティを使用すると、プログラミング時にずさんなことができます。正しく使用すると、コンパイラは実行時に検出するのではなく、作成したエラーを検出します。これがどれほど重要であるかを十分に強調することはできません。結局のところ、コードはテストケースでまったく呼び出されないかもしれませんが、コンパイラは常にそれを評価します。
すべて一緒に置く
それで...すべてをまとめましょう。私たちが欲しい:
これは、どのコレクションでもかなり低レベルの要件です... IL/CPUへの変換だけでなく、特定の方法でメモリを構成する必要があります...それが基本型と見なされる正当な理由があると思います。
互換性。配列は、ジェネリックがなかった時代に遡る歴史的なタイプです。
今日では、Array
、次にArray<T>
、次に特定のクラス;)
したがって、なぜそうではないのか知りたい:
その理由は、C#の最初のバージョンにはジェネリックが存在しなかったためです。
しかし、私は自分で何が問題になるのか理解できません。
問題は、Array
クラスを使用する大量のコードが破損することです。 C#は多重継承をサポートしないため、次のような行
Array ary = Array.Copy(.....);
int[] values = (int[])ary;
壊れるでしょう。
MSが最初からC#と.NETを作り直した場合、おそらくArray
をジェネリッククラスにするのに問題はないでしょうが、それは現実ではありません。
人々が言及した他の問題に加えて、一般的な_Array<T>
_を追加しようとすると、他のいくつかの困難が生じます。
ジェネリックが導入された瞬間から今日の共分散機能が存在していたとしても、配列には十分ではありませんでした。 _Car[]
_を並べ替えるように設計されたルーチンは、配列の要素をCar
型の要素にコピーしてからコピーして戻す必要がある場合でも、_Buick[]
_を並べ替えることができます。 。タイプCar
から_Buick[]
_への要素のコピーは、実際にはタイプセーフではありませんが、便利です。ソートを可能にするような方法で、共変配列の1次元配列インターフェースを定義することができます[e.g. `Swap(int firstIndex、int secondIndex)method]を含めることにより、しかし、配列と同じくらい柔軟な何かを作ることは難しいでしょう。
_Array<T>
_型は_T[]
_に対してうまく機能するかもしれませんが、汎用型システム内では_T[]
_、_T[,]
_、_T[,,]
_、_T[,,,]
_など、任意の数の添え字用。
.netには、2つの型を同一と見なす必要があるという概念を表現する手段がありません。そのため、型_T1
_の変数は、型_T2
_のいずれかにコピーできます。同じオブジェクトへの参照を保持します。 _Array<T>
_型を使用する人は、おそらく_T[]
_を期待するコードにインスタンスを渡し、_T[]
_を使用するコードからインスタンスを受け入れたいと思うでしょう。古いスタイルの配列を新しいスタイルを使用するコードとの間でやり取りできない場合、新しいスタイルの配列は機能というよりも障害になります。
型システムをジンクスして、本来の動作をする_Array<T>
_型を許可する方法があるかもしれませんが、そのような型は、他の一般的な型とはまったく異なる多くの方法で動作し、既に型があるため目的の動作(つまり_T[]
_)を実装するため、別の定義によってどのような利点が得られるかは明確ではありません。
誰もが言っているように、元のArray
はジェネリックではありません。なぜなら、v1でジェネリックが存在していなかったからです。以下の推測...
「配列」をジェネリックにする(今では意味があります)には、
既存のArray
を保持し、汎用バージョンを追加します。これは素晴らしいことですが、「配列」のほとんどの使用法は、時間の経過とともに成長することを伴い、同じ概念List<T>
のより良い実装が代わりに実装されたと考えられます。この時点で、「成長しない要素の連続リスト」の汎用バージョンを追加しても、あまり魅力的ではありません。
非ジェネリックArray
を削除し、同じインターフェースを持つジェネリックArray<T>
実装に置き換えます。次に、既存のArray
型ではなく新しい型で動作するように、古いバージョン用にコンパイルされたコードを作成する必要があります。フレームワークのコードがこのような移行をサポートすることは可能ですが(おそらく難しい)、他の人が書いたコードは常にたくさんあります。
Array
は非常に基本的なタイプなので、ほとんどすべての既存のコード(リフレクションを備えたカスタムコードとネイティブコードとCOMによるマーシャリングを含む)がそれを使用します。その結果、バージョン間(1.x-> 2.xの.Net Framework)のわずかな非互換性の価格も非常に高くなります。
そのため、結果としてArray
型は永久に存在し続けます。現在、使用する汎用的な同等物としてList<T>
があります。
何かが足りないかもしれませんが、配列インスタンスがICollection、IEnumerableなどにキャストまたは使用されない限り、Tの配列では何も得られません。
配列は高速であり、すでにタイプセーフであり、ボクシング/アンボクシングのオーバーヘッドは発生しません。