2013-10-30 40 views
2

爲什麼Enumerator不跟蹤同一個函數中的項目,但是如果MoveNext操作在其他函數中發生則不會跟蹤該項目?枚舉器和函數示波器

例子:

public static void Test() 
    { 
     var array = new List<Int32>(new Int32[] { 1, 2, 3, 4, 5 }); 
     var e = array.GetEnumerator(); 
     e.MoveNext(); 
     e.MoveNext(); 
     Console.WriteLine(e.Current); // 2 
     Incremenet(e); 
     Console.WriteLine(e.Current); //2 
    } 

    static void Incremenet(IEnumerator<Int32> e) 
    { 
     Console.WriteLine("Inside " + e.Current); //2 
     e.MoveNext(); 
     Console.WriteLine("Inside " + e.Current); // 3 
     e.MoveNext(); 
     Console.WriteLine("Inside " + e.Current); //4 
    } 

我期待得到5在過去的CW,但我得到2,就像是從來沒有增加。當函數返回時,爲什麼Increment函數內的MoveNext被遺忘了?

乾杯。

回答

6

List<T>的枚舉器類型List<T>.Enumerator不是class,而是struct。由於GetEnumerator暴露,返回類型爲List<T>.Enumerator,當您使用vare的類型是List<T>.Enumerator,所以當你把它傳遞給Incremenet,它會自動爲boxed是一個IEnumerator<Int32>對象。這是你看到的奇怪行爲的原因。

如果鍵入e作爲IEnumerator<Int32>,拳擊,只要你得到的對象發生,所以這種奇怪的行爲不會發生:它的工作原理相同,你是否在TestIncrement運行其他代碼(我固定順便說一句,這不是「Incremenet」)。

public static void Test() 
{ 
    var array = new List<Int32> { 1, 2, 3, 4, 5 }; 
    IEnumerator<Int32> e = array.GetEnumerator(); // boxed here 
    e.MoveNext(); 
    e.MoveNext(); 
    Console.WriteLine(e.Current); // 2 
    Increment(e); 
    Console.WriteLine(e.Current); // now it's 4 
} 

static void Increment(IEnumerator<Int32> e) 
{ 
    Console.WriteLine("Inside " + e.Current); // 2 
    e.MoveNext(); 
    Console.WriteLine("Inside " + e.Current); // 3 
    e.MoveNext(); 
    Console.WriteLine("Inside " + e.Current); // 4 
} 

由於性能原因,它被暴露爲類型而不是IEnumerator<T>foreachis smart enough在這種情況下無需裝箱或虛擬調度,即可調用MoveNextCurrent,並且可以毫無問題地處理值類型語義。正如你所看到的,它確實會造成混淆,因爲當你不十分關心你如何處理它時,因爲mutable structs are evil

+1

他們不得不在外部公開它。如果他們沒有把它暴露在外面,那麼它總會被裝箱,如果它總是被裝箱的話,它可能剛開始就是一個班級。目的是它實際上總是被一個'foreach'使用,它可以和值類型語義正確地工作。如果結構沒有公開暴露,那麼'foreach'不能在沒有裝箱的情況下抽出枚舉器。 (至少沒有特殊的編譯器支持,比如對陣列完成) – Servy

+0

@Servy好點,好信息,我已經改變了我的答案。 –

+0

@Servy我仍然不清楚爲什麼它被做成一個'struct'而不是'''''密封'/非'虛擬'的東西。這可以讓你生成接近完全相同的IL,我認爲它具有相似的性能。 –

1

出於同樣的原因test在下面的測試用例增加後爲1。這是值類型的正常行爲。

static void Main(string[] args) 
    { 
     int test = 1; 
     Increment(test); 
     Console.WriteLine("After increment: " + test); 
    } 

    static void Increment(int test)//add ref and the original variable will also update 
    { 
     test += 1; 
     Console.WriteLine(test); 
    } 

正如Servy指出在技術上,該例子並不同之處在於局部變量test是不可變的。實際上,我們看到的行爲是因爲變量被複制到Increment方法。但是,我的觀點是,這種類型的行爲在值類型(屬性和局部變量)之間是一致的。爲進一步證明這一事實:

struct MutableStruct 
{ 
    public int EvilInt { get; set; }  
} 

class Program 
{   
    static void Main(string[] args) 
    { 
     var testStruct = new MutableStruct(); 
     testStruct.EvilInt = 1; 

     int test = 1; 
     Increment(test, testStruct); 
     Console.WriteLine("After increment: " + test + " and..." + testStruct.EvilInt);//both 1 
    } 

    static void Increment(int test, MutableStruct test2) 
    { 
     test2.EvilInt += 1; 
     test += 1; 
     Console.WriteLine(test + " and..." + test2.EvilInt);//both 2 
    } 
} 

正如我們在這裏看到的,這種行爲在值類型中是正常的。在地方不可變價值類型和可變結構的情況下,行爲都保持一致。

+0

這不是真的:你的測試失敗了,因爲你正在給'test'分配一個新的值,這個值是按值傳遞的。這不是他用他的統計員做的事情。 – dcastro

+0

@dcastro爲什麼結構體做他們所做的事情的細節更好留給學習C#規範的人來完成。值類型的範圍和邪惡可變性是我認爲在這裏相關的。 –

+0

但就是這樣。您的示例演示了* immutable *值類型的語義。列表枚舉器是一個* mutable *值類型。這個答案甚至沒有提到這一點。 – Servy