Iteriert foreach () durch Referenz?

71

Bedenken Sie:

List<MyClass> obj_list = get_the_list();
foreach( MyClass obj in obj_list )
{
    obj.property = 42;
}

Befindet sich objein Verweis auf das entsprechende Objekt in der Liste, sodass beim Ändern der Eigenschaft die Änderung in der Objektinstanz bestehen bleibt, sobald sie irgendwo erstellt wurde?

Sharkin
quelle

Antworten:

69

Ja, objist ein Verweis auf das aktuelle Objekt in der Sammlung (vorausgesetzt, es MyClasshandelt sich tatsächlich um eine Klasse). Wenn Sie Eigenschaften über die Referenz ändern, ändern Sie das Objekt wie erwartet.

Beachten Sie jedoch, dass Sie die Variable objselbst nicht ändern können, da es sich um die Iterationsvariable handelt. Wenn Sie es versuchen, wird ein Kompilierungsfehler angezeigt. Das bedeutet, dass Sie es nicht auf Null setzen können und wenn Sie Werttypen iterieren, können Sie keine Mitglieder ändern, da dies den Wert ändern würde.

In der C # -Sprachenspezifikation heißt es (8.8.4)

"Die Iterationsvariable entspricht einer schreibgeschützten lokalen Variablen mit einem Bereich, der sich über die eingebettete Anweisung erstreckt."

Brian Rasmussen
quelle
1
Dies gilt auch für Werttypen, dh Strukturen.
technophile
7
@technophile: Tatsächlich können Sie obj nicht ändern, wenn MyClass eine Struktur ist, da Sie die Iterationsvariable selbst nicht ändern dürfen.
Brian Rasmussen
@ Brian Rasmussen: Warum können wir das aufzählbare Objekt nicht ändern?
Amit
2
@Amy: Gemäß der Sprachspezifikation (8.8.4) "Die Iterationsvariable entspricht einer schreibgeschützten lokalen Variablen mit einem Bereich, der sich über die eingebettete Anweisung erstreckt."
Brian Rasmussen
öffentliche Klasse MyClass {public int val; } class MyApp {public statisch void Main (String [] args) {IList <MyClass> list = new List <MyClass> (); MyClass obj1 = new MyClass (); obj1.val = 10; list.Add (obj1); MyClass obj2 = new MyClass (); obj2.val = 10; list.Add (obj2); foreach (MyClass obj in Liste) {obj.val = 20; }}} verweisen auf diesen Code. Dies funktioniert;)
Amit
22

Ja, bis Sie den generischen Typ von List in IEnumerable ändern.

jaccso
quelle
7
Möchte jemand darauf näher eingehen?
Justin Obney
3
Ich würde eine weitere Erklärung leben, warum dies der Fall ist
Sarah Bailey
1
Ich denke, es könnte etwas damit zu tun haben: stackoverflow.com/a/8064426/1054322
MatrixManAtYrService
@MatrixManAtYrService ist in Bezug auf den Beispielfall, auf den hier verwiesen wird, wahrscheinlich korrekt, wobei die IEnumerable auf neu erstellte Objekte einwirkt. Meines Wissens gibt es keinen Unterschied zwischen IEnumerable und List.
Sean
21

Sie haben hier zwei verschiedene Fragen gestellt. Nehmen wir sie in die richtige Reihenfolge.

Iteriert eine foreach-Schleife als Referenz?

Wenn Sie im gleichen Sinne wie eine C ++ for-Schleife als Referenz meinen, dann nein. C # hat keine lokalen Variablenreferenzen im gleichen Sinne wie C ++ und unterstützt daher diese Art der Iteration nicht.

Wird die Änderung bestehen bleiben?

Angenommen, MyClass ist ein Referenztyp, lautet die Antwort Ja. Eine Klasse ist ein Referenztyp in .Net, und daher ist die Iterationsvariable eine Referenz auf die eine Variable, keine Kopie. Dies würde für einen Werttyp nicht zutreffen.

JaredPar
quelle
17

Nun, es ist mir passiert, dass meine Änderungen nicht in einer foreach-Schleife aktualisiert wurden, als ich die var-Sammlung durchlaufen habe:

var players = this.GetAllPlayers();
foreach (Player player in players)
{
    player.Position = 1;
}

Als ich var in List änderte, fing es an zu funktionieren.

mrzepa
quelle
Das ist interessant. Ich frage mich, ob die Sammlung in einem solchen Fall geklont wird.
Sharkin
Bei Verwendung von foreach ... gibt es 2 Fälle. 1. Es könnte in IEnumerable / IEnumerable <T> umgewandelt werden, wenn Ihre Klasse es implementiert. Wenn Ihre Sammlung eine Struktur ist, wird sie eingerahmt. 2. Verwenden Sie die GetEnumerator () -Methode direkt. Es ist eine Art Methodenmuster. Es ermöglicht grundlegende Enumeratoren für Werttypen ohne teures Boxen. IEnumerable ist überhaupt nicht erforderlich. Aber dann natürlich keine Linq (wir brauchen Eigenschaften oder irgendeine Art von Wertschnittstellen).
Arek Bal
9

Sie können dies in diesem Fall (mit a List<T>) tun, aber wenn Sie über das Generikum iterieren IEnumerable<T>, wird es von seiner Implementierung abhängig.

Wenn es immer noch ein List<T>oder T[]wäre, würde alles wie erwartet funktionieren. Das große Problem entsteht, wenn Sie mit einem Produkt arbeiten IEnumerable<T>, das mit Ertrag konstruiert wurde. In diesem Fall können Sie die Eigenschaften Teiner Iteration nicht mehr ändern und erwarten, dass sie vorhanden sind, wenn Sie sie erneut iterieren IEnumerable<T>.

J Keegan
quelle
4

Dies gilt, solange es sich nicht um eine Struktur handelt.

Tiefgefroren
quelle
1
Oh, das ist gut zu wissen! Beim Lesen der Top-Antworten habe ich mich gefragt, warum mein Code nicht funktioniert hat, aber das erklärt es. Die Struktur wurde in eine Klasse geändert und * magic * funktioniert.
Luc
4

Vielleicht ist es für Sie interessant zu verstehen, dass es ab Version C # 7.3 möglich ist, Werte durch Referenz zu ändern, vorausgesetzt, die Current- Eigenschaft des Enumerators gibt einen Referenztyp zurück. Folgendes wäre gültig (wörtliche Kopie aus den MS-Dokumenten):

Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
    item = num++;
}

Weitere Informationen zu dieser neuen Funktion finden Sie unter C # foreach-Anweisung | Microsoft Docs .

Wolf
quelle
2

Nun, ohne genau zu verstehen, was Sie unter "Durch Referenz iterieren" verstehen, kann ich nicht spezifisch mit Ja oder Nein antworten, aber ich kann sagen, dass unter der Oberfläche vorgeht, dass das .net-Framework eine "Enumerator" -Klasse für erstellt Jedes Mal, wenn der Client-Code ein foreach für die Lebensdauer des foreach aufruft, das einen Referenzzeiger in der Sammlung enthält, über die iteriert wird, und jedes Mal, wenn Ihr foreach iteriert, "liefert" ir ein Element und "erhöht" den Zeiger oder die Referenz in der Enumerator zum nächsten Punkt ...

Dies geschieht unabhängig davon, ob es sich bei den Elementen in der Sammlung, über die Sie iterieren, um Werttypen oder Referenztypen handelt.

Charles Bretana
quelle
0

obj ist ein Verweis auf ein Element in der Liste. Wenn Sie also dessen Wert ändern, bleibt es bestehen. Nun sollten Sie sich Sorgen machen, ob get_the_list (); erstellt eine tiefe Kopie der Liste oder gibt dieselbe Instanz zurück.

Stan R.
quelle
1
Es macht keinen Unterschied für die Funktionalität, an der er interessiert ist. Vier Kopien derselben Liste verweisen alle auf dieselben tatsächlichen Objekte. Unabhängig davon, ob Sie sie in Liste1 oder Liste3 ändern, wird entweder das ursprüngliche Objekt geändert. Das einzige, was davon betroffen ist, ist, dass das Hinzufügen oder Entfernen von Elementen zur / von der Liste keine Kopien der Liste betrifft.
Technophile
Ja, die kurze Antwort lautet natürlich "Ja, es ist ein Ref-Typ", aber die Probleme, auf die er stoßen könnte, sind, wenn er eine Kopie einer Originalliste erstellt. Wenn er also get_the_list () aufruft; ändert es und ruft dann get_the_list () auf; Wieder an einer anderen Stelle im Code erhält er möglicherweise nicht die erwarteten Ergebnisse.
Stan R.
0

Ja, deshalb können Sie das aufzählbare Objekt auch nicht im Kontext der foreach-Anweisung ändern.

Brian Scott
quelle