2010-11-23 17 views
4

Hier Aufzählen ist ein einfaches Szenario in C#:Unterschiede zwischen Powershell und C#, wenn Sie eine Sammlung

var intList = new List<int>(); 
intList.Add(4); 
intList.Add(7); 
intList.Add(2); 
intList.Add(9); 
intList.Add(6); 

foreach (var num in intList) 
{ 
    if (num == 9) 
    { 
    intList.Remove(num); 
    Console.WriteLine("Removed item: " + num); 
    } 

    Console.WriteLine("Number is: " + num); 
} 

Dies wirft ein InvalidOperationException, weil ich die Sammlung bin modifizieren, während er aufzählt.

Betrachten wir nun ähnlich Powershell-Code:

$intList = 4, 7, 2, 9, 6 

foreach ($num in $intList) 
{ 
    if ($num -eq 9) 
    { 
    $intList = @($intList | Where-Object {$_ -ne $num}) 
    Write-Host "Removed item: " $num 
    } 

    Write-Host "Number is: " $num 
} 

Write-Host $intList 

Dieses Skript entfernt tatsächlich die Nummer 9 aus der Liste! Keine Ausnahmen ausgelöst.

Nun, ich weiß, dass das C# -Beispiel ein List-Objekt verwendet, während das PowerShell-Beispiel ein Array verwendet, aber wie zählt PowerShell eine Auflistung auf, die während der Schleife geändert wird?

Antwort

2

Die Antwort bereits von @Sean gegeben ist, ich sendet nur die Code, der zeigt, dass die ursprüngliche Sammlung während foreach nicht geändert wird: es zählt durch die ursprüngliche Sammlung und es gibt keinen Widerspruch daher.

# original array 
$intList = 4, 7, 2, 9, 6 

# make another reference to be used for watching of $intList replacement 
$anotherReferenceToOriginal = $intList 

# prove this: it is not a copy, it is a reference to the original: 
# change [0] in the original, see the change through its reference 
$intList[0] = 5 
$anotherReferenceToOriginal[0] # it is 5, not 4 

# foreach internally calls GetEnumerator() on $intList once; 
# this enumerator is for the array, not the variable $intList 
foreach ($num in $intList) 
{ 
    [object]::ReferenceEquals($anotherReferenceToOriginal, $intList) 
    if ($num -eq 9) 
    { 
     # this creates another array and $intList after assignment just contains 
     # a reference to this new array, the original is not changed, see later; 
     # this does not affect the loop enumerator and its collection 
     $intList = @($intList | Where-Object {$_ -ne $num}) 
     Write-Host "Removed item: " $num 
     [object]::ReferenceEquals($anotherReferenceToOriginal, $intList) 
    } 

    Write-Host "Number is: " $num 
} 

# this is a new array, not the original 
Write-Host $intList 

# this is the original, it is not changed 
Write-Host $anotherReferenceToOriginal 

Ausgang:

5 
True 
Number is: 5 
True 
Number is: 7 
True 
Number is: 2 
True 
Removed item: 9 
False 
Number is: 9 
False 
Number is: 6 
5 7 2 6 
5 7 2 9 6 

Wir können sehen, dass $intList geändert wird, wenn wir "ein Element entfernen". Es bedeutet nur, dass diese Variable jetzt einen Verweis auf ein neues Array enthält, es ist die Variable geändert, nicht das Array. Die Schleife setzt die Aufzählung des ursprünglichen Arrays fort, das nicht geändert wird, und $anotherReferenceToOriginal enthält immer noch einen Verweis darauf.

+0

Markieren Sie dies als Antwort, weil es die Dinge vollständig erklärt. :-) –

3

Das Foreach Konstrukt wertet die Liste auf Vollständigkeit aus und speichert das Ergebnis in einer temporären Variablen, bevor es mit der Iteration beginnt. Wenn Sie diese tatsächliche Entfernung durchführen, aktualisieren Sie $ intList, um auf eine neue Liste zu verweisen. Mit anderen Worten: in tatsächlich so etwas wie dies unter der Haube zu tun:

$intList = 4, 7, 2, 9, 6 

$tempList=$intList 
foreach ($num in $tempList) 
{ 
    if ($num -eq 9) 
    { 
    $intList = @($intList | Where-Object {$_ -ne $num}) 
    Write-Host "Removed item: " $num 
    } 

    Write-Host "Number is: " $num 
} 

Write-Host $intList 

Ihr Aufruf:

$intList = @($intList | Where-Object {$_ -ne $num}) 

tatsächlich schafft eine völlig neue Liste mit dem Wert entfernt.

Wenn Sie die Entfernungslogik ändern, um das letzte Element in der Liste zu entfernen (6), dann werden Sie feststellen, dass es immer noch gedruckt wird, obwohl Sie glauben, dass es wegen der temporären Kopie entfernt wurde.

+0

Die Antwort von JaredPar zeigt an, dass das foreach-Konstrukt KEINE temporäre Listenkopie erstellt. – Greg

+0

@Greg: Nein, JaredPar verwendet eine ArrayList. Es gibt nichts im Powershell-Code, um anzuzeigen, dass die Liste eine List <> oder ArrayList ist, das ist ein Implementierungsdetail. – Sean

+0

Ich bin nicht vertraut genug mit Powershell zu wissen: ist das "foreach-Konstrukt", das Sie in Ihrer Antwort von der Zeile 'foreach ($ num in $ intList) 'oder der Zeile, die' Where-Object {$ _ -ne $ num} '. Ich hatte das erstere angenommen. – Greg

3

Das Problem hier ist, dass Sie nicht gleichwertige Codebeispiele vergleichen. Im Powershell-Beispiel erstellen Sie eine neue Liste, anstatt die Liste wie im C# -Beispiel zu ändern. Hier ist eine Probe, die auf den ursprünglichen C# näher in der Funktionalität ist ein

$intList = new-object System.Collections.ArrayList 
$intList.Add(4) 
$intList.Add(7) 
$intList.Add(2) 
$intList.Add(9) 
$intList.Add(6) 

foreach ($num in $intList) { 
    if ($num -eq 9) { 
    $intList.Remove($num) 
    Write-Host "Removed item: " $num 
    } 

    Write-Host "Number is: " $num 
} 

Write-Host $intList 

Und bei der Ausführung erzeugt er den gleichen Fehler

Number is: 4 
Number is: 7 
Number is: 2 
Removed item: 9 
Number is: 9 
An error occurred while enumerating through a collection: Collection was modifi 
ed; enumeration operation may not execute.. 
At C:\Users\jaredpar\temp\test.ps1:10 char:8 
+ foreach <<<< ($num in $intList) 
    + CategoryInfo   : InvalidOperation: (System.Collecti...numeratorSi 
    mple:ArrayListEnumeratorSimple) [], RuntimeException 
    + FullyQualifiedErrorId : BadEnumeration 

4 7 2 6 
Verwandte Themen