2013-08-31 9 views
12

mit weiß, dass ich die Grundlagen, wie foreach-Schleifen arbeiten in C# (How do foreach loops work in C#)Zuordnung Speicher, wenn foreach-Schleifen in C#

Ich frage mich, ob foreach Speicher reserviert, die Garbage Collection verursachen kann? (für alle eingebauten Systemtypen).

Zum Beispiel mit Reflektor auf der System.Collections.Generic.List<T> Klasse, hier ist die Umsetzung von GetEnumerator:

public Enumerator<T> GetEnumerator() 
{ 
    return new Enumerator<T>((List<T>) this); 
} 

Bei jeder Nutzung dieser einen neuen Enumerator zuordnet (und mehr Müll).

Haben alle Arten dies zu tun? Wenn ja warum? (Kann nicht ein einzelner Enumerator wiederverwendet werden?)

+2

a) Dies ist ein Implementierungsdetail. Diese sind irrelevant. b) Der GC existiert. –

+1

Ich denke, das Hauptproblem mit einzelnen Enumerator wäre Thread-Sicherheit. – volpav

+2

Dies ist nicht auf 'foreach()' beschränkt. Die ganze Idee eines GC ist, dass kleine, kurzlebige Objekte sehr billig sind. Wir "verschwenden" sie ständig, wie in 'text = text.ToLower();'. Mach dir keine Sorgen darüber. Die meisten Wiederverwendungsschemata erweisen sich als kostspieliger. –

Antwort

37

Foreach kann Zuweisungen verursachen, aber zumindest in den neueren Versionen .NET und Mono, tut es nicht, wenn Sie mit den konkreten System.Collections.Generic Typen oder Arrays zu tun haben. Ältere Versionen dieser Compiler (z. B. die von Unity3D bis 5.5 verwendete Version von Mono) generieren immer Zuweisungen.

Der C# -Compiler verwendet duck typing, um nach einer GetEnumerator()-Methode zu suchen, und verwendet diese, wenn möglich. Die meisten GetEnumerator() Methoden auf System.Collection.Generic Typen haben GetEnumerator() -Methoden, die Strukturen zurückgeben, und Arrays werden speziell behandelt. Wenn Ihre GetEnumerator()-Methode nicht zuweist, können Sie normalerweise Zuweisungen vermeiden.

Allerdings werden Sie immer eine Zuteilung erhalten, wenn Sie mit einer der Schnittstellen zu tun IEnumerable, IEnumerable<T>, IList oder IList<T>. Selbst wenn Ihre implementierende Klasse eine Struktur zurückgibt, wird die Struktur eingerahmt und in IEnumerator oder IEnumerator<T> umgewandelt, was eine Zuweisung erfordert.


HINWEIS: Da 5.5 Unity zu C# 6 aktualisiert, kenne ich keine aktuellen Compiler Version, die noch diese zweite Zuordnung hat.

Es gibt eine zweite Zuweisung, die ein wenig komplizierter zu verstehen ist. Nehmen Sie diese foreach-Schleife:

List<int> valueList = new List<int>() { 1, 2 }; 
foreach (int value in valueList) { 
    // do something with value 
} 

Bis C# 5.0, dehnt es sich um so etwas wie dies (mit gewissen kleinen Unterschieden):

List<int>.Enumerator enumerator = valueList.GetEnumerator(); 
try { 
    while (enumerator.MoveNext()) { 
     int value = enumerator.Current; 
     // do something with value 
    } 
} 
finally { 
    IDisposable disposable = enumerator as System.IDisposable; 
    if (disposable != null) disposable.Dispose(); 
} 

Während List<int>.Enumerator eine Struktur ist, und muss nicht sein auf dem Heap zugewiesen, die Besetzung enumerator as System.IDisposable Boxen die Struktur, die eine Zuordnung ist. Die Spezifikation änderte sich mit C# 5.0 und verbot die Zuweisung, aber .NET broke the spec und optimierte die Zuweisung früher.

Diese Zuweisungen sind äußerst gering. Beachten Sie, dass sich eine Zuweisung von einem Speicherverlust unterscheidet, und bei der Garbage Collection müssen Sie sich im Allgemeinen keine Gedanken darüber machen. Es gibt jedoch einige Szenarien, in denen Sie sich selbst um diese Zuordnungen kümmern. Ich arbeite an Unity3D und bis 5.5 konnten wir keine Zuteilungen in Operationen vornehmen, die bei jedem Spielframe passieren, denn wenn der Garbage Collector ausgeführt wird, bekommen Sie einen spürbaren Ruck.

Beachten Sie, dass Foreach-Schleifen in Arrays speziell behandelt werden und nicht Dispose aufrufen müssen. Soweit ich das beurteilen kann, hat foreach niemals zugewiesen, wenn Arrays durchlaufen werden.

+4

Willkommen bei SO, und Glückwunsch für eine so gute erste Antwort. Es ist ungewöhnlich genug, darauf hingewiesen zu werden. –

+0

Warum haben Sie gesagt, dass TestHash nicht zuteilt, wenn Sie in der IL-Liste den gleichen Cast für IDisposable wie in TestIList sehen können? Oder meintest du, dass es vor der Schleife keine Zuweisung gibt, aber immer noch eine danach? Kannst du das bitte klären? –

+0

Vielleicht verwirrt das ildasm das Problem jetzt. Als ich die Frage zum ersten Mal beantwortete, kannte ich nicht die ganze Geschichte über die IDisposable Allokationen. Das ildasm zeigt nur modernes Visual Studio, das nicht mehr durch "Idisposable" zugewiesen wird. Wenn es da wäre, würden Sie eine Box-Anweisung sehen. Stattdessen wird die Zuweisung auf der IList in dem Aufruf 'GetEnumerator' begraben, da sie ihren Rückgabewert auf 'IEnumerator 'setzen muss. – hangar

1

Da ein enumerator halten, die aktuelle Position hält. Es ist wie ein Cursor im Vergleich zu Datenbanken. Wenn mehrere Threads auf denselben Enumerator zugreifen würden, würden Sie die Kontrolle über die Sequenz verlieren. Und Sie müssten es jedes Mal auf den ersten Punkt zurücksetzen, wenn eine foreach es konsultiert.

1

Wie in den Kommentaren erwähnt, sollte dies in der Regel kein Problem, Sie kümmern müssen, wie dass der Punkt der Garbage Collection. Das heißt, mein Verständnis ist, dass ja, jede foreach-Schleife wird ein neues Enumerator-Objekt generieren, das schließlich Garbage Collected sein wird. Um zu sehen, warum, schauen Sie in die Dokumentation für die Schnittstelle here. Wie Sie sehen, gibt es einen Funktionsaufruf, der das nächste Objekt anfordert. Die Fähigkeit, dies zu tun, bedeutet, dass der Enumerator einen Zustand hat und wissen muss, welcher der nächste ist. Wie, warum dies notwendig ist, Bildmotiv, wodurch Sie eine Interaktion zwischen jeder Permutation der Sammlung Items:

foreach(var outerItem in Items) 
{ 
    foreach(var innterItem in Items) 
    { 
     // do something 
    } 
} 

Hier haben Sie zwei Aufzählungen auf der gleichen Sammlung zur gleichen Zeit. Ein gemeinsamer Standort würde Ihr Ziel natürlich nicht erreichen.

5

Nein, verursacht Garbage Collection keine Liste aufzählt.

Der Enumerator für die List<T> Klasse aus dem Heap keinen Speicher zuweisen. Es ist eine Struktur, keine Klasse, daher weist der Konstruktor kein Objekt zu, sondern gibt nur einen Wert zurück. Der Code foreach würde diesen Wert auf dem Stapel speichern, nicht auf dem Heap.

Enumeratoren für andere Sammlungen können jedoch Klassen sein, die auf dem Heap ein Objekt zuweisen würde. Sie müssen den Typ des Enumerators für jeden Fall überprüfen, um sicherzugehen.