【问题标题】:IEnumerator<XElement> behaviourIEnumerator<XElement> 行为
【发布时间】:2017-10-15 13:59:50
【问题描述】:

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

  • 为什么即使列表已用尽,它仍继续打印&lt;c&gt;
  • 为什么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>

【问题讨论】:

  • 为了将来参考,这里的标签的目的是为了引起对您的问题的注意。您的问题只会被监视“xelement”和“ienumerator”标签的人看到——几乎没有人看到。我在原始的新问题列表中偶然看到了它,但是出现了如此多的问题,以至于任何人都不太可能注意到任何特定的问题。如果您将其标记为 C#(我冒昧),您就会在可能回答它的人面前得到它。我意识到这似乎有点违反直觉,但这是该网站最终被使用的方式。
  • 感谢 Ed 的建议。我会记住的。我很幸运你偶然发现了我的问题,因为你的答案很完美:)

标签: c# .net xelement ienumerator


【解决方案1】:

为什么 x.Current 从不等于 null?

简短回答:正如 Brian Kernighan 所说,“您的编译器是该语言的最终权威”1。它之所以如此,是因为微软的人就是这样写代码的。正如我们将在下面看到的,在这种情况下,这取决于实施者。用传奇计算机科学家 Ray Dorset2 的话来说,“随心所欲”。

但是这里还有一些有趣的东西。 According to MSDN,

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

这是通用的IEnumerator&lt;T&gt;,您正在使用的那个。这就是你问题的答案。

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

以上内容与nongeneric IEnumerator 的记录行为不同:

如果最后一次调用 MoveNext 返回 false,则调用 Current 会引发异常。

它告诉你的是,使用通用 IEnumerator&lt;T&gt;,在 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() 为我们提供了一个枚举器,其CurrentMoveNext() 返回false 后返回default(T)。这对于上面的GetEnumerator() 来说确实是多余的,但它说明了值类型而不是引用类型会发生什么。

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

我们通过调用ToList() 得到的List&lt;T&gt; 枚举器也在结束后从Current 返回default(T)

我没有找到一个通用的IEnumerator&lt;T&gt; 在结束后抛出Current,但正如你所见,我没有找到一个伟大的项目。


1 或类似的词;我找不到确切的措辞。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-09
    • 1970-01-01
    • 1970-01-01
    • 2021-05-07
    • 2010-09-18
    • 2010-10-22
    相关资源
    最近更新 更多