2013-09-30 5 views
6

Trotz der Tatsache, dass IEnumerator.Reset method should never be used Ich seltsames Verhalten der Methode Implementierung innerhalb List<T> gefunden.Liste <T> .Enumerator IEnumerator.Reset() Methode Implementierung

Egal, wie Sie .NET Framework-Quellcode untersuchen (versucht, mit Referenzquelle und ILSpy) wird das Verfahren implementiert, wie folgend:

void System.Collections.IEnumerator.Reset() { 
    if (version != list._version) { 
     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
    } 

    index = 0; 
    current = default(T); 
} 

aber es sieht aus wie das Verfahren überhaupt nicht genannt wird! Betrachten Sie den Code:

var list = new List<int>(1) { 3 }; 
using (var e = list.GetEnumerator()) 
{ 
    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 

    ((IEnumerator)e).Reset(); 

    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 
} 

Es ist ziemlich klar,, dass es True und 3 zweimal gedruckt werden soll. Stattdessen ist das Ergebnis

True 
3 
False 
0 

Jede einfache Erklärung, die ich vermisse?

+0

stellte sich heraus, ein neues Beispiel dafür, warum wandelbar structs böse sind. Veränderliche Strukturen, bei denen die Mutation durch eine explizite Schnittstellenimplementierung geschieht, sind nur ein kleines Extra-Übel. Diese verschachtelten'Enumerator'-Strukturen werden normalerweise nur vom C# -Compiler verwendet, wenn sie eine 'foreach'-Anweisung übersetzen. –

Antwort

14

Jede einfache Erklärung, die ich vermisse?

Ja: Sie Boxen die List.Enumerator hier:

((IEnumerator)e).Reset(); 

dass eine Kopie der vorhandenen nimmt und setzt sie - das Original in einem Stück zu verlassen.

den tatsächlichen enumerator So setzen Sie so etwas wie diese brauchen würden:

var list = new List<int>(1) { 3 }; 
var e = list.GetEnumerator(); 
// Can't use "ref" with a using statement 
try 
{ 
    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 

    Reset(ref e); 

    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 
} 
finally 
{ 
    e.Dispose(); 
} 

static void Reset<T>(ref T enumerator) where T : IEnumerator 
{ 
    enumerator.Reset(); 
} 

Es ist schwierig, weil es explizite Schnittstellenimplementierung verwendet.

Ich habe es nicht getestet, aber ich denke, das sollte für Sie arbeiten. Natürlich ist es eine schlechte Idee, tun, um diese ...

EDIT: Alternativ nur Ihren Variablentyp ändern, um IEnumerator oder IEnumerator<int> zu beginnen. Dann wird es einmal verpackt werden, und die Reset Methode wird die boxed Wert mutieren:

var list = new List<int>(1) { 3 }; 
using (IEnumerator e = list.GetEnumerator()) 
{ 
    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 

    e.Reset(); 

    Console.WriteLine(e.MoveNext()); 
    Console.WriteLine(e.Current); 
} 
+0

Sie müssen es auch aus dem 'using' herausnehmen, um es als Referenz zu übergeben. Abgesehen von dieser Änderung habe ich es sehr schnell getestet und das funktioniert. – Servy

+1

@Servy hat Recht. Er kann es einfach am Anfang einpacken und dann die ganze Box wieder benutzen. Bearbeiten: Zum Beispiel, wenn er zuerst die 'List <>' auf den Interface-Typ 'IEnumerable <>' oder 'IEnumerable' hochlädt, dann verwendet die' GetEnumerator() 'Methode dieser Schnittstelle, der Enumerator wird bereits in einem Feld sein, wenn er bekommt es, und er kann diese Box durch das ganze Beispiel wiederverwenden. –

+0

Sicher, du hast Recht. Ich habe es soeben getestet, indem ich 'IEnumerator' der Variablen zuordnete und sie wird tatsächlich zurückgesetzt. Vielen Dank. Ich werde die Antwort so schnell wie möglich akzeptieren. – MarcinJuraszek