枚举器行为会根据我们的引用方式而改变吗?

Amy*_*Amy 4 c# ienumerable

在类中包含对列表枚举器的引用似乎改变了它的行为.匿名类的示例:

public static void Main()
{
    var list = new List<int>() { 1, 2, 3 };
    var an = new { E = list.GetEnumerator() };
    while (an.E.MoveNext())
    {
        Debug.Write(an.E.Current);
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望这打印"123",但它只打印零,永远不会终止.具体类的相同示例:

public static void Main()
{
    var list = new List<int>() { 1, 2, 3 };
    var an = new Foo()
    {
        E = list.GetEnumerator()
    };

    while (an.E.MoveNext())
    {
        Debug.Write(an.E.Current);
    }
}

public class Foo
{
    public List<int>.Enumerator E { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?

Ren*_*ogt 6

我测试了它,对我来说它也不适用于你的具体课程.

其原因是,List<T>.Enumerator是一个可变的 structan.E是一个属性.

编译器为每个自动属性生成一个支持字段,如下所示:

public class Foo
{
    private List<int>.Enumerator _E;
    public List<int>.Enumerator get_E() { return E; }
    public void set_E(List<int>.Enumerator value) { E = value; }
}
Run Code Online (Sandbox Code Playgroud)

A struct是值类型,因此每次访问时an.E都会获得该值的副本.

当您打电话MoveNext()Current,您在该副本上调用它时,此副本会发生变异.

下次访问时an.E,MoveNext()或者Current您获得了尚未迭代的枚举器新副本.

an.E.Current0,而不是1因为-再-你会得到一个新的枚举MoveNext()尚未呼吁.


如果要存储列表枚举器的引用,可以Foo使用类型的属性声明类IEnumerator<int>:

public class Foo
{
    public IEnumerator<int> E { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

如果现在分配E = list.GetEnumerator();,枚举器将被装箱并存储引用而不是.