it-swarm.com.de

Warum ist Array kein generischer Typ?

Array ist wie folgt deklariert: 

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

Ich frage mich, warum es nicht so ist: 

public partial class Array<T>
    : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
  1. Was wäre das Problem, wenn es zu einem generischen Typ erklärt würde? 

  2. Wenn es sich um einen generischen Typ handelt, brauchen wir dann noch den nicht generischen Typ? Und wenn wir das tun würden, könnte es nur von Array<T> (wie oben angenommen) abgeleitet werden? 

    public partial class Array: Array<object> { 
    

Update:  

Der zweite Punkt ist auch wichtig für die Annahme, dass das nicht-generische Array nicht länger ein Basistyp ist. 

49
Ken Kin

[Update, neue Erkenntnisse, bis jetzt fehlte etwas]

In Bezug auf die frühere Antwort:

  • Arrays sind wie andere Typen kovariant. Sie können Dinge wie 'object [] foo = new string [5];' implementieren. mit Kovarianz, das ist also nicht der Grund.
  • Die Kompatibilität ist wahrscheinlich der Grund dafür, das Design nicht zu überdenken, aber ich behaupte, dass dies auch nicht die richtige Antwort ist.

Der andere Grund, den ich mir vorstellen kann, ist, dass ein Array der "Basistyp" für eine lineare Menge von Elementen im Speicher ist. Ich habe darüber nachgedacht, Array <T> zu verwenden. Hier könnten Sie sich auch fragen, warum T ein Objekt ist und warum dieses 'Objekt' überhaupt existiert. In diesem Szenario ist T [] genau das, was ich für eine andere Syntax für Array <T> halte, die mit Array kovariant ist. Da sich die Typen tatsächlich unterscheiden, betrachte ich die beiden Fälle als ähnlich.

Beachten Sie, dass sowohl ein Basisobjekt als auch ein Basisarray keine Anforderungen für eine OO Sprache sind. C++ ist dafür das perfekte Beispiel. Die Einschränkung, für diese Basiskonstrukte keinen Basistyp zu haben, besteht darin, dass mit Arrays oder Objekten unter Verwendung von Reflexion nicht gearbeitet werden kann. Für Objekte, die Sie daran gewöhnt sind, Foo-Dinge zu erstellen, die ein 'Objekt' natürlich erscheinen lassen. In der Realität macht es das Fehlen einer Array-Basisklasse ebenso unmöglich, Foo auszuführen - was nicht so häufig verwendet wird, aber für das Paradigma gleichermaßen wichtig ist.

Aus diesem Grund ist es IMO unmöglich, C # ohne Array-Basistyp, aber mit einer Fülle von Laufzeittypen (insbesondere Reflektion) zu haben.

Also mehr ins Detail ...

Wo werden Arrays verwendet und warum sind sie Arrays

Einen Basistyp für etwas so Grundlegendes wie ein Array zu haben, wird für viele Dinge aus gutem Grund verwendet:

  • Einfache Arrays

Ja, wir wussten bereits, dass Leute T[] verwenden, genau wie sie List<T> verwenden. Beide implementieren einen gemeinsamen Satz von Schnittstellen, um genau zu sein: IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection und IEnumerable.

Sie können problemlos ein Array erstellen, wenn Sie dies wissen. Wir alle wissen auch, dass dies wahr ist, und es ist nicht aufregend, also machen wir weiter ...

  • Sammlungen erstellen.

Wenn Sie in die Liste eintauchen, erhalten Sie irgendwann ein Array - um genau zu sein: ein T [] -Array.

Warum ist das so? Sie hätten eine Zeigerstruktur (LinkedList) verwenden können, dies ist jedoch nicht dasselbe. Listen sind fortlaufende Speicherblöcke und erhalten ihre Geschwindigkeit, indem sie ein fortlaufender Speicherblock sind. Das hat viele Gründe, aber einfach ausgedrückt: Die Verarbeitung von kontinuierlichem Speicher ist die schnellste Methode zur Verarbeitung von Speicher - es gibt sogar Anweisungen in Ihrer CPU, die ihn schneller machen.

Ein aufmerksamer Leser weist möglicherweise darauf hin, dass Sie hierfür kein Array benötigen, sondern einen fortlaufenden Block von Elementen des Typs 'T', den IL versteht und verarbeiten kann. Mit anderen Worten, Sie könnten den Array-Typ hier entfernen, solange Sie sicherstellen, dass es einen anderen Typ gibt, der von IL verwendet werden kann, um dasselbe zu tun.

Beachten Sie, dass es Wert- und Klassentypen gibt. Um die bestmögliche Leistung beizubehalten, müssen Sie sie als solche in Ihrem Block speichern ... aber für die Rangierung ist es einfach eine Voraussetzung.

  • Marshalling.

Marshalling verwendet Basistypen, auf deren Kommunikation sich alle Sprachen einigen. Diese Grundtypen sind Dinge wie Byte, Int, Float, Pointer ... und Array. Am bemerkenswertesten ist die Art und Weise, wie Arrays in C/C++ verwendet werden:

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

Grundsätzlich setzt dies einen Zeiger am Anfang des Arrays und erhöht den Zeiger (mit der Größe von (Foo) Bytes), bis er das Ende des Arrays erreicht. Das Element wird bei * foo abgerufen - wodurch das Element abgerufen wird, auf das der Zeiger 'foo' zeigt.

Beachten Sie erneut, dass es Werttypen und Referenztypen gibt. Sie möchten wirklich kein MyArray, das einfach alles speichert, was als Objekt verpackt ist. Die Implementierung von MyArray ist um einiges schwieriger geworden.

Einige aufmerksame Leser können hier auf die Tatsache hinweisen, dass Sie hier nicht wirklich ein Array benötigen, was wahr ist. Sie benötigen einen zusammenhängenden Elementblock vom Typ Foo - und wenn es sich um einen Werttyp handelt, muss dieser als (Byte-Darstellung des) Werttyps im Block gespeichert werden.

  • Mehrdimensionale Arrays

Also mehr ... Was ist mit Multidimensionalität? Anscheinend sind die Regeln nicht mehr so ​​schwarz-weiß, weil wir plötzlich nicht mehr alle Basisklassen haben:

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

Starker Typ ist gerade aus dem Fenster verschwunden, und Sie haben am Ende die Sammlungstypen IList, ICollection und IEnumerable. Hey, wie sollen wir dann die Größe bekommen? Bei Verwendung der Array-Basisklasse hätten wir Folgendes verwenden können:

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

... aber wenn wir uns Alternativen wie IList anschauen, gibt es kein Äquivalent. Wie lösen wir das? Sollte hier ein IList<int, int> eingefügt werden? Dies ist sicherlich falsch, da der Basistyp nur int ist. Was ist mit IMultiDimentionalList<int>? Wir können das tun und es mit den Methoden auffüllen, die aktuell in Array sind.

  • Arrays haben eine feste Größe

Haben Sie bemerkt, dass es spezielle Aufrufe für die Neuzuweisung von Arrays gibt? Dies hat alles mit Speicherverwaltung zu tun: Arrays sind so niedrig, dass sie nicht verstehen, was Wachstum oder Verkleinerung sind. In C würden Sie hierfür 'malloc' und 'realloc' verwenden, und Sie sollten wirklich Ihr eigenes 'malloc' und 'realloc' implementieren, um zu verstehen, warum es wichtig ist, für alle genau feste Größen zu haben Dinge, die Sie direkt zuweisen.

Wenn Sie es sich ansehen, gibt es nur ein paar Dinge, die in einer "festen" Größe zugewiesen werden: Arrays, alle grundlegenden Werttypen, Zeiger und Klassen. Anscheinend behandeln wir Arrays anders, genauso wie wir Basistypen anders behandeln.

Eine Randnotiz zur Typensicherheit

Warum also brauchen diese alle diese "Access Point" -Schnittstellen überhaupt?

In allen Fällen empfiehlt es sich, den Benutzern einen sicheren Zugangspunkt zu bieten. Dies kann durch den Vergleich von Code wie folgt veranschaulicht werden:

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

so codieren:

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

Typensicherheit ermöglicht es Ihnen, beim Programmieren schlampig zu sein. Bei korrekter Verwendung findet der Compiler den Fehler, wenn Sie einen gemacht haben, anstatt die Laufzeit herauszufinden. Ich kann gar nicht genug betonen, wie wichtig das ist - schließlich wird Ihr Code in einem Testfall möglicherweise überhaupt nicht aufgerufen, während der Compiler ihn immer auswertet!

Alles zusammen

Also ... lassen Sie uns alles zusammenfassen. Wir wollen:

  • Ein stark typisierter Datenblock
  • Das hat seine Daten ständig gespeichert
  • IL-Unterstützung, um sicherzustellen, dass wir die coolen CPU-Anweisungen verwenden können, die dafür sorgen, dass es schnell blutet
  • Eine gemeinsame Schnittstelle, die alle Funktionen verfügbar macht
  • Geben Sie Sicherheit
  • Multidimensionalität
  • Wir möchten, dass Werttypen als Werttypen gespeichert werden
  • Und die gleiche Rangierstruktur wie jede andere Sprache da draußen
  • Und eine feste Größe, da dies die Speicherzuweisung erleichtert

Das sind ziemlich niedrige Anforderungen für jede Sammlung ... es erfordert eine bestimmte Organisation des Speichers sowie die Konvertierung in IL/CPU ... Ich würde sagen, es gibt einen guten Grund, warum sie als Basistyp betrachtet wird.

12
atlaste

Kompatibilität. Array ist ein historischer Typ, der auf die Zeit zurückgeht, als es keine Generika gab.

Heute wäre es sinnvoll, Array zu haben, dann Array<T>, dann die spezifische Klasse;)

11
TomTom

Daher würde ich gerne wissen, warum es nicht so ist:

Der Grund ist, dass Generika in der ersten Version von C # nicht vorhanden waren.

Aber ich kann nicht herausfinden, was das Problem selbst wäre.

Das Problem ist, dass dadurch eine große Menge Code zerstört wird, der die Array-Klasse verwendet. C # unterstützt keine Mehrfachvererbung, also solche Zeilen

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

wäre kaputt.

Wenn MS C # und .NET von Grund auf neu erstellen würde, wäre es wahrscheinlich kein Problem, Array zu einer generischen Klasse zu machen, aber das ist nicht die Realität.

5
JLRishe

Wie jeder sagt - original Array ist nicht generisch, da es in v1 keine Generika gab, als es entstanden ist. Spekulation unten ...

Um "Array" generisch zu machen (was jetzt sinnvoll wäre), können Sie entweder

  1. Array beibehalten und generische Version hinzufügen. Dies ist Nizza, aber die meisten Verwendungen von "Array" umfassen das Wachstum im Laufe der Zeit und es ist wahrscheinlich der Grund, dass eine bessere Implementierung desselben Konzepts List<T> stattdessen implementiert wurde. Das Hinzufügen einer generischen Version der "sequentiellen Liste der Elemente, die nicht wachsen" erscheint an diesem Punkt nicht sehr ansprechend.

  2. entfernen Sie nicht generische Array und ersetzen Sie sie durch die generische Array<T>-Implementierung mit derselben Schnittstelle. Jetzt müssen Sie kompilierten Code für ältere Versionen erstellen, um mit dem neuen Typ anstelle des vorhandenen Array-Typs arbeiten zu können. Zwar wäre es (möglicherweise auch sehr schwer) möglich, dass Rahmencode eine solche Migration unterstützt, es gibt jedoch immer eine Menge Code, den andere Personen schreiben. 

    Da Array ein sehr grundlegender Typ ist, wird praktisch jeder vorhandene Code (der benutzerdefinierten Code mit Reflektion und Marshalling mit nativem Code und COM enthält) verwendet. Als Ergebnis wäre der Preis von selbst geringfügigen Inkompatibilitäten zwischen Versionen (1.x -> 2.x von .Net Framework) sehr hoch. 

Als Ergebnis ist Array type für immer da. Wir haben jetzt List<T> als generisches Äquivalent zur Verwendung.

2
Alexei Levenkov

Zusätzlich zu den anderen Problemen, die Leute erwähnt haben, würde der Versuch, ein generisches Array<T> hinzuzufügen, einige andere Schwierigkeiten aufwerfen:

  • Selbst wenn die heutigen Kovarianzfunktionen bereits zu dem Zeitpunkt bestanden hätten, als Generika eingeführt wurden, wären sie für Arrays nicht ausreichend gewesen. Eine Routine, die ein Car[] sortieren soll, kann ein Buick[] sortieren, selbst wenn Elemente aus dem Array in Elemente des Typs Car kopiert und anschließend zurückkopiert werden müssen. Das Kopieren des Elements vom Typ Car zurück in ein Buick[] ist nicht wirklich typsicher, aber nützlich. Man könnte eine eindimensionale Array-Schnittstelle eines kovarianten Arrays so definieren, dass ein Sortieren möglich ist [z. durch Einfügen einer `Swap-Methode (int firstIndex, int secondIndex)], aber es wäre schwierig, etwas zu erstellen, das so flexibel ist wie Arrays.

  • Während ein Array<T>-Typ für ein T[] gut funktionieren könnte, gibt es innerhalb des generischen Typsystems keine Möglichkeit, eine Familie zu definieren, die T[], T[,], T[,,], T[,,,] usw. für eine beliebige Anzahl von Unterschriften enthält.

  • Es gibt keine Möglichkeit in .net, die Vorstellung auszudrücken, dass zwei Typen als identisch betrachtet werden sollten, so dass eine Variable des Typs T1 in einen Typ vom Typ T2 kopiert werden kann und umgekehrt, wobei beide Variablen Verweise auf dasselbe Objekt enthalten. Jemand, der einen Array<T>-Typ verwendet, möchte möglicherweise Instanzen an Code übergeben können, der T[] erwartet, und Instanzen aus Code akzeptieren, der T[] verwendet. Wenn Arrays im alten Stil nicht an und von Code übertragen werden konnten, der den neuen Stil verwendet, sind die Arrays im neuen Stil eher ein Hindernis als eine Funktion.

Es kann Wege geben, das Typsystem zu verknüpfen, um einen Typ Array<T> zuzulassen, der sich so verhält, wie er sollte, aber ein solcher Typ würde sich auf viele Arten verhalten, die sich von anderen generischen Typen völlig unterschieden, und da es bereits einen Typ gibt, der das gewünschte implementiert Verhalten (dh T[]) ist nicht klar, welche Vorteile sich aus der Definition eines anderen ergeben würden.

2
supercat

Vielleicht fehlt mir etwas, aber wenn die Array-Instanz nicht in ICollection, IEnumerable usw. umgewandelt oder verwendet wird, gewinnen Sie nichts mit einem Array von T.

Arrays sind schnell und sind bereits typsicher und verursachen keinen Box-/Unboxing-Aufwand.

0
user1758003