IEnumerator <xelement>行为

时间:2017-05-16 13:03:07

标签: c# .net xelement ienumerator

我使用以下程序枚举xml输入中的元素。当达到最终元素时 x.MoveNext() 返回false,这部分很清楚。我的问题是:

  • 为什么即使列表用尽也会继续打印<c>
  • 为什么x.Current永远不会等于null?这就是我所期望的,因为XElement是引用类型

    static void Main(string[] args)
    {
        var x = GetXml();
        while (true)
        {
            var isMoving = x.MoveNext();
            Console.WriteLine($"Cursor moved: {isMoving}");
            Console.WriteLine($"Current element is null: {x.Current == null}");
            Console.WriteLine($"Current element: {x.Current}" );
        }
    }
    
    static IEnumerator<XElement> GetXml()
    {
        return XElement.Parse(@"<x>
                                  <a></a>
                                  <b></b>
                                  <c></c>
                                </x>").Elements().GetEnumerator();
    }
    

输出:

    Cursor moved: true
    Current element is null: False 
    Current element: <a></a>
    Cursor moved: true
    Current element is null: False 
    Current element: <b></b>
    Cursor moved: true
    Current element is null: False 
    Current element: <c></c>
    Cursor moved: False                 <- confusing bit 
    Current element is null: False 
    Current element: <c></c>

1 个答案:

答案 0 :(得分:1)

  

为什么x.Current永远不等于null?

简短回答:正如Brian Kernighan所说,“你的编译器是语言” 1 的最终权威。它做了它做的事情,因为微软的家伙以这种方式编写代码。正如我们将在下面看到的那样,在这种情况下,这取决于实施者。用传说中的计算机科学家Ray Dorset 2 的话来说,“就是按照你的感受去做”。

但这里仍有一些有趣的东西。 According to MSDN

  

如果对MoveNext的最后一次调用返回false,则Current未定义。

那是你正在使用的通用IEnumerator<T>。这是你问题的答案。

在某些语言中,例如JavaScript,“undefined”是关键字。在其他情况下,它是一个简单的英语单词,用于描述规范留下的内容。如果他们说C或C#中的行为是“未定义的”,则表示由实施者决定。

以上内容与nongeneric IEnumerator

的记录行为不同
  

如果对MoveNext的最后一次调用返回false,则调用Current会抛出异常。

所有它告诉你的是,使用通用IEnumerator<T>MoveNext()返回false后,Current可以执行实现者所做的任何事情。它可以返回default(T)(这就是我要做的),但它可以返回最后一项。我会毫不犹豫地让它抛出一个异常,因为我们有多个Microsoft .NET代码(你发现的那个,以及你将要看到的几个)不抛出的情况, MSDN并没有说它可能,所以这意味着它只是没有。

I tested some other cases

public class Program
{
    public static void Main()
    {
        //var x = GetEnumeratorNonGeneric(1);
        //var x = GetEnumerator(1);
        //var x = GetEnumeratorInt(1);
        //var x = GetEnumeratorIntRangeSelect(1);
        var x = GetEnumeratorIntList(1);

        for (int i = 0; i < 2; ++i)
        {
            var isMoving = x.MoveNext();
            Console.WriteLine($"Cursor moved: {isMoving}");
            Console.WriteLine($"Current element is null: {x.Current == null}");
            Console.WriteLine($"Current element: {x.Current}" );
        }
    }

    static IEnumerator<String> GetEnumerator(int count)
    {
        return Enumerable.Range(1, count).Select(n => n.ToString()).GetEnumerator();
    }

    static IEnumerator<int> GetEnumeratorInt(int count)
    {
        return Enumerable.Range(1, count).GetEnumerator();
    }

    static IEnumerator<int> GetEnumeratorIntRangeSelect(int count)
    {
        return Enumerable.Range(1, count).Select(n => n).GetEnumerator();
    }

    static IEnumerator<int> GetEnumeratorIntList(int count)
    {
        return Enumerable.Range(1, count).ToList().GetEnumerator();
    }

    static IEnumerator GetEnumeratorNonGeneric(int count)
    {

        return new ArrayList(Enumerable.Range(1, count).Select(n => n.ToString()).ToArray()).GetEnumerator();
    }
}

GetEnumeratorNonGeneric()的输出:

Cursor moved: True
Current element is null: False
Current element: 1
Cursor moved: False
Run-time exception (line -1): Enumeration already finished.

Stack Trace:

[System.InvalidOperationException: Enumeration already finished.]

GetEnumerator()的输出。这不会抛出,但在这种情况下确实会返回default(T)null

Cursor moved: True
Current element is null: False
Current element: 1
Cursor moved: False
Current element is null: True
Current element:

GetEnumeratorInt()的输出。 Enumerable.Range()返回使用Current的最后一个值的枚举器:

Cursor moved: True
Current element is null: False
Current element: 1
Cursor moved: False
Current element is null: False
Current element: 1

使用GetEnumeratorIntRangeSelect(),我们发现Select()为我们提供了一个枚举器,Currentdefault(T)返回false后返回MoveNext()。这与上面的GetEnumerator()实际上是多余的,但它说明了值类型而不是引用类型会发生什么。

Cursor moved: True
Current element is null: False
Current element: 1
Cursor moved: False
Current element is null: False
Current element: 0

我们从调用List<T>获得的ToList()枚举器也会在结束后从default(T)返回Current

我没有找到在IEnumerator<T>结束后抛出的通用Current,但正如您所看到的,我没有做出一个寻找一个的伟大项目。

1 或者说是这样的话;我找不到确切的措辞。

2 可能不是真正的计算机科学家。