Wie initialisiere ich eine Liste <T> auf eine bestimmte Größe (im Gegensatz zur Kapazität)?

110

.NET bietet einen generischen Listencontainer, dessen Leistung nahezu identisch ist (siehe Frage Leistung von Arrays vs. Listen). Sie unterscheiden sich jedoch in der Initialisierung erheblich.

Arrays lassen sich sehr einfach mit einem Standardwert initialisieren und haben per Definition bereits eine bestimmte Größe:

string[] Ar = new string[10];

So kann man sicher zufällige Elemente zuweisen, sagen wir:

Ar[5]="hello";

mit Liste sind die Dinge schwieriger. Ich kann zwei Möglichkeiten sehen, um dieselbe Initialisierung durchzuführen, von denen Sie keine als elegant bezeichnen würden:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

oder

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Was wäre ein sauberer Weg?

BEARBEITEN: Die bisherigen Antworten beziehen sich auf die Kapazität, die etwas anderes ist als das Vorabfüllen einer Liste. Bei einer Liste, die gerade mit einer Kapazität von 10 erstellt wurde, ist dies beispielsweise nicht möglichL[2]="somevalue"

EDIT 2: Die Leute fragen sich, warum ich Listen auf diese Weise verwenden möchte, da dies nicht die Art ist, wie sie verwendet werden sollen. Ich kann zwei Gründe sehen:

  1. Man könnte ziemlich überzeugend argumentieren, dass Listen die Arrays der "nächsten Generation" sind, die Flexibilität ohne Strafe hinzufügen. Daher sollte man sie standardmäßig verwenden. Ich weise darauf hin, dass sie möglicherweise nicht so einfach zu initialisieren sind.

  2. Was ich gerade schreibe, ist eine Basisklasse, die Standardfunktionen als Teil eines größeren Frameworks bietet. In der von mir angebotenen Standardfunktionalität ist die Größe der Liste in Advanced bekannt, weshalb ich ein Array hätte verwenden können. Ich möchte jedoch jeder Basisklasse die Möglichkeit bieten, sie dynamisch zu erweitern, und entscheide mich daher für eine Liste.

Boas
quelle
1
"BEARBEITEN: Die bisherigen Antworten beziehen sich auf die Kapazität, die etwas anderes ist als das Vorabfüllen einer Liste. Beispielsweise kann man auf einer Liste, die gerade mit einer Kapazität von 10 erstellt wurde, nicht L [2] =" somevalue "ausführen Änderung, vielleicht sollten Sie den Titel der Frage
umformulieren
Aber was nützt es, eine Liste mit leeren Werten vorab zu füllen, denn genau das versucht der Topicstarter zu tun?
Frederik Gheysels
1
Wenn die Positionszuordnung so wichtig ist, wäre es dann nicht sinnvoller, ein Wörterbuch <int, string> zu verwenden?
Greg D
7
Listist kein Ersatz für Array. Sie lösen deutlich getrennte Probleme. Wenn Sie eine feste Größe wünschen, möchten Sie eine Array. Wenn Sie a verwenden List, machen Sie es falsch.
Zenexer
5
Ich finde immer erschwerende Antworten, die versuchen, Argumente wie "Ich kann nicht verstehen, warum ich jemals ..." brauchen würde. Es bedeutet nur das: man konnte es nicht sehen. Es bedeutet nicht unbedingt etwas anderes. Ich respektiere, dass die Leute bessere Ansätze für ein Problem vorschlagen wollen, aber es sollte demütiger formuliert werden, z. B. "Sind Sie sicher, dass Sie eine Liste brauchen? Vielleicht, wenn Sie uns mehr über Ihr Problem erzählen ...". Auf diese Weise wird es angenehm, engagiert und ermutigt das OP, seine Frage zu verbessern. Sei ein Gewinner - sei demütig.
AnorZaken

Antworten:

79

Ich kann nicht sagen, dass ich das sehr oft brauche - könnten Sie mehr Details darüber geben, warum Sie das wollen? Ich würde es wahrscheinlich als statische Methode in eine Hilfsklasse einfügen:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Sie könnten verwenden, Enumerable.Repeat(default(T), count).ToList()aber das wäre aufgrund der Puffergrößenänderung ineffizient.

Wenn Tes sich um einen Referenztyp handelt, werden countKopien der für den valueParameter übergebenen Referenz gespeichert, sodass alle auf dasselbe Objekt verweisen. Das kann je nach Anwendungsfall das sein, was Sie wollen oder nicht.

BEARBEITEN: Wie in den Kommentaren erwähnt, können Sie Repeateddie Liste mit einer Schleife füllen, wenn Sie möchten. Das wäre auch etwas schneller. Persönlich finde ich den Code Repeatbeschreibender und vermute, dass in der realen Welt der Leistungsunterschied irrelevant wäre, aber Ihr Kilometerstand kann variieren.

Jon Skeet
quelle
1
Mir ist klar, dass dies ein alter Beitrag ist, aber ich bin neugierig. Enumerable.Repeat-Tarife sind im Vergleich zu einer for-Schleife gemäß dem letzten Teil des Links ( dotnetperls.com/initialize-array ) viel schlechter . Auch AddRange () hat eine O (n) -Komplexität gemäß msdn. Ist es nicht etwas kontraproduktiv, die angegebene Lösung anstelle einer einfachen Schleife zu verwenden?
Jimmy
@ Jimmy: Beide Ansätze werden O (n) sein, und ich finde, dass dieser Ansatz beschreibender ist, was ich erreichen möchte. Wenn Sie eine Schleife bevorzugen, können Sie diese gerne verwenden.
Jon Skeet
@Jimmy: Beachten Sie auch , dass der Benchmark wird mit Enumerable.Repeat(...).ToArray(), das ist nicht , wie ich es bin mit.
Jon Skeet
1
Ich habe Enumerable.Repeat()das folgendermaßen verwendet ( Pairgenau wie das C ++ - Äquivalent implementiert ): Als Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)ich den Nebeneffekt bemerkte, war das Listvoll von Kopien, auf die für ein einzelnes Objekt verwiesen wurde. Durch Ändern eines Elements wird myList[i].First = 1jedes einzelne Element im Ganzen geändert List. Ich habe Stunden gebraucht, um diesen Fehler zu finden. Kennt ihr eine Lösung für dieses Problem (außer nur eine gemeinsame Schleife zu verwenden und zu verwenden .Add(new Pair...)?
00zetti
1
@ Pac0, ich habe gerade eine Antwort unten hinzugefügt. Die Änderungen können abgelehnt werden.
Jeremy Ray Brown
135
List<string> L = new List<string> ( new string[10] );

quelle
3
Ich persönlich denke, dies ist der sauberste Weg - obwohl er im
Fragetext
4
+1: Treffen Sie einfach eine Situation, in der eine Liste mit variabler Größe mit einem festen Satz von Nullen initialisiert werden musste, bevor ich sie über einen Index auffüllte (und anschließend Extras hinzufügte, sodass ein Array ungeeignet war). Diese Antwort wird von mir als praktisch und einfach eingestuft.
Gone Coding
Fabelhafte Antwort. @GoneCoding Ich habe mich nur gefragt, ob die neu initialisierte Liste intern Leinfach den Speicher von string[10]as is wiederverwendet (der Sicherungsspeicher einer Liste ist auch ein Array) oder einen eigenen neuen Speicher zuweist und dann den Inhalt von kopiert string[10]. string[10]wird automatisch Müll gesammelt, wenn Ldie spätere Route ausgewählt wird.
RBT
3
@RBT Dies ist der einzige Nachteil dieses Ansatzes: Das Array wird nicht wiederverwendet, sodass Sie momentan zwei Kopien des Arrays haben (List verwendet Arrays intern, wie Sie anscheinend wissen). In seltenen Fällen kann dies ein Problem sein, wenn Sie eine große Anzahl von Elementen oder Speicherbeschränkungen haben. Und ja, das zusätzliche Array ist für die Speicherbereinigung berechtigt, sobald der List-Konstruktor fertig ist (sonst wäre es ein schrecklicher Speicherverlust gewesen). Beachten Sie, dass "berechtigt" nicht "sofort gesammelt" bedeutet, sondern das nächste Mal, wenn die Speicherbereinigung ausgeführt wird.
AnorZaken
2
Diese Antwort weist 10 neue Zeichenfolgen zu und wiederholt sie dann, indem sie nach Bedarf kopiert werden. Wenn Sie mit großen Arrays arbeiten; Dann denken Sie nicht einmal daran, da es doppelt so viel Speicher benötigt wie die akzeptierte Antwort.
UKMonkey
22

Verwenden Sie den Konstruktor, der ein int ("Kapazität") als Argument verwendet:

List<string> = new List<string>(10);

EDIT: Ich sollte hinzufügen, dass ich Frederik zustimme. Sie verwenden die Liste auf eine Weise, die gegen die gesamte Begründung verstößt, die dahinter steckt.

EDIT2:

EDIT 2: Was ich gerade schreibe, ist eine Basisklasse, die Standardfunktionen als Teil eines größeren Frameworks bietet. In der von mir angebotenen Standardfunktionalität ist die Größe der Liste in Advanced bekannt, weshalb ich ein Array hätte verwenden können. Ich möchte jedoch jeder Basisklasse die Möglichkeit bieten, sie dynamisch zu erweitern, und entscheide mich daher für eine Liste.

Warum sollte jemand die Größe einer Liste mit allen Nullwerten kennen müssen? Wenn die Liste keine realen Werte enthält, würde ich eine Länge von 0 erwarten. Die Tatsache, dass dies ungeschickt ist, zeigt jedenfalls, dass dies gegen die beabsichtigte Verwendung der Klasse verstößt.

Ed S.
quelle
46
Diese Antwort weist nicht 10 Null-Einträge in der Liste zu (was die Anforderung war), sondern weist lediglich Platz für 10 Einträge zu, bevor eine Größenänderung der Liste erforderlich ist (dh Kapazität), sodass sich dies in Bezug auf new List<string>()das Problem nicht unterscheidet . Gut gemacht,
Gone Coding
2
Dieser überladene Konstruktor ist der Wert für "Anfangskapazität", nicht für "Größe" oder "Länge", und er initialisiert auch die Elemente nicht
Matt Wilko,
Um zu antworten: "Warum sollte jemand das brauchen?": Ich brauche das jetzt zum tiefen Klonen von Baumdatenstrukturen. Ein Knoten kann seine untergeordneten Knoten füllen oder nicht, aber meine Basisknotenklasse muss in der Lage sein, sich selbst mit allen untergeordneten Knoten zu klonen. Bla, bla, es ist noch komplizierter. Aber ich muss sowohl meine noch leere Liste über list[index] = obj;füllen als auch einige andere Listenfunktionen verwenden.
Bitterblue
3
@Bitterblue: Auch jede Art von vorab zugewiesenem Mapping, bei dem möglicherweise nicht alle Werte im Voraus angezeigt werden. Es gibt sicherlich Verwendungen; Ich habe diese Antwort in meinen weniger erfahrenen Tagen geschrieben.
Ed S.
8

Erstellen Sie zuerst ein Array mit der Anzahl der gewünschten Elemente und konvertieren Sie das Array dann in eine Liste.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
mini998
quelle
7

Warum verwenden Sie eine Liste, wenn Sie sie mit einem festen Wert initialisieren möchten? Ich kann verstehen, dass Sie aus Gründen der Leistung eine anfängliche Kapazität haben möchten, aber ist dies nicht einer der Vorteile einer Liste gegenüber einem regulären Array, das bei Bedarf erweitert werden kann?

Wenn Sie dies tun:

List<int> = new List<int>(100);

Sie erstellen eine Liste mit einer Kapazität von 100 Ganzzahlen. Dies bedeutet, dass Ihre Liste erst wachsen muss, wenn Sie das 101. Element hinzufügen. Das zugrunde liegende Array der Liste wird mit einer Länge von 100 initialisiert.

Frederik Gheysels
quelle
1
"Warum verwenden Sie eine Liste, wenn Sie sie mit einem festen Wert initialisieren möchten?" Ein guter Punkt.
Ed S.
7
Er bat um eine Liste, in der jedes Element initialisiert wurde und die Liste eine Größe hatte, nicht nur eine Kapazität. Diese Antwort ist derzeit falsch.
Nic Foster
Die Frage erfordert eine Antwort, keine Kritik daran, gestellt zu werden. Manchmal gibt es einen Grund, dies auf diese Weise zu tun, anstatt ein Array zu verwenden. Wenn Sie daran interessiert sind, warum dies der Fall sein könnte, können Sie eine Frage zum Stapelüberlauf stellen.
Dino Dini
5

Das Initialisieren des Inhalts einer solchen Liste ist nicht wirklich das, wofür Listen gedacht sind. Listen dienen zur Aufnahme von Objekten. Wenn Sie bestimmten Objekten bestimmte Zahlen zuordnen möchten, sollten Sie anstelle einer Liste eine Schlüssel-Wert-Paar-Struktur wie eine Hash-Tabelle oder ein Wörterbuch verwenden.

Welbog
quelle
Dies beantwortet die Frage nicht, sondern gibt lediglich einen Grund dafür, dass sie nicht gestellt wird.
Dino Dini
5

Sie scheinen die Notwendigkeit einer Positionszuordnung zu Ihren Daten zu betonen. Wäre ein assoziatives Array also nicht passender?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D.
quelle
Dies beantwortet eine andere Frage als die gestellte.
Dino Dini
4

Wenn Sie die Liste mit N Elementen eines festen Werts initialisieren möchten:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B.
quelle
Siehe John Skeets Antwort zur Besorgnis über die Größenänderung des Puffers durch diese Technik.
Amy B
1

Mit Linq können Sie Ihre Liste geschickt mit einem Standardwert initialisieren. (Ähnlich wie die Antwort von David B. )

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Gehen Sie einen Schritt weiter und initialisieren Sie jede Zeichenfolge mit unterschiedlichen Werten: "Zeichenfolge 1", "Zeichenfolge 2", "Zeichenfolge 3" usw.:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
quelle
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
quelle
Wie wäre es mit List <string> temp2 = new List <string> (temp); Wie das OP bereits vorgeschlagen.
Ed S.
Ed - dieser beantwortet tatsächlich die Frage - bei der es nicht um Kapazität geht.
Boaz
Das OP erklärte jedoch bereits, dass ihm diese Lösung nicht gefallen habe.
Ed S.
1

Die akzeptierte Antwort (die mit dem grünen Häkchen) weist ein Problem auf.

Das Problem:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Ich empfehle, die obige Zeile zu ändern, um eine Kopie des Objekts zu erstellen. Es gibt viele verschiedene Artikel darüber:

Wenn Sie jedes Element in Ihrer Liste mit dem Standardkonstruktor und nicht mit NULL initialisieren möchten, fügen Sie die folgende Methode hinzu:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }
Jeremy Ray Brown
quelle
1
Es ist kein "Problem" - es ist das normale Verhalten beim Kopieren von Referenzen. Ohne die Situation zu kennen, können Sie nicht wissen, ob es geeignet ist, das Objekt zu kopieren. Bei der Frage geht es nicht darum, ob Listen mit Kopien gefüllt werden sollen oder nicht - es geht darum, eine Liste mit einer Kapazität zu initialisieren. Ich werde meine Antwort in Bezug auf das Verhalten, das es hat , klarstellen , aber ich denke wirklich nicht, dass es im Kontext dieser Frage als Problem gilt.
Jon Skeet
@ JonSkeet, ich habe auf den statischen Helfer geantwortet, der für primitive Typen, aber nicht für Referenztypen in Ordnung ist. Ein Szenario, in dem eine Liste mit demselben Element initialisiert wird, auf das verwiesen wird, scheint nicht richtig zu sein. Warum sollte in diesem Szenario die Liste überhaupt vorhanden sein, wenn jedes Element auf dasselbe Objekt auf dem Heap verweist?
Jeremy Ray Brown
Beispielszenario: Sie möchten eine Liste von Zeichenfolgen füllen, zunächst mit dem Eintrag "Unbekannt" für jedes Element, und dann die Liste für bestimmte Elemente ändern. In diesem Fall ist es durchaus sinnvoll, dass alle "Unbekannten" Werte auf dieselbe Zeichenfolge verweisen. Es wäre sinnlos, die Zeichenfolge jedes Mal zu klonen. Das Auffüllen einer Zeichenfolge mit mehreren Verweisen auf dasselbe Objekt ist im Allgemeinen nicht "richtig" oder "falsch". Solange die Leser wissen, wie sich das Verhalten verhält, müssen sie entscheiden, ob es ihrem spezifischen Anwendungsfall entspricht .
Jon Skeet
0

Ein Hinweis zu IList: MSDN IList Anmerkungen : "IList-Implementierungen lassen sich in drei Kategorien einteilen: schreibgeschützt, feste Größe und variable Größe. (...). Die generische Version dieser Schnittstelle finden Sie unter System.Collections.Generic.IList<T>."

IList<T>erbt NICHT von IList( List<T>implementiert aber beide IList<T>und IList), hat aber immer eine variable Größe . Seit .NET 4.5 haben wir aber auch IReadOnlyList<T>AFAIK, es gibt keine generische Liste mit fester Größe, nach der Sie suchen würden.

EricBDev
quelle
0

Dies ist ein Beispiel, das ich für meinen Unit-Test verwendet habe. Ich habe eine Liste von Klassenobjekten erstellt. Dann habe ich forloop verwendet, um die 'X'-Anzahl von Objekten hinzuzufügen, die ich vom Dienst erwarte. Auf diese Weise können Sie eine Liste für eine bestimmte Größe hinzufügen / initialisieren.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

Ich hoffe, ich habe euch geholfen.

Abhay Shiro
quelle
-1

Ein bisschen spät, aber die erste Lösung, die Sie vorgeschlagen haben, scheint mir viel sauberer zu sein: Sie weisen den Speicher nicht zweimal zu. Sogar List Constrcutor muss das Array durchlaufen, um es zu kopieren. es weiß nicht einmal im Voraus, dass nur Nullelemente darin sind.

1. - N zuordnen - Schleife N Kosten: 1 * Zuweisen (N) + N * Schleifen_iteration

2. - N zuweisen - N + loop () zuweisen Kosten: 2 * (N) + N * loop_iteration zuweisen

Die Zuweisung von List und Loops in List kann jedoch schneller sein, da List eine integrierte Klasse ist, aber C # sooo jit-kompiliert ist ...

Victor Drouin
quelle