C#“生成器”方法

时间:2016-07-08 20:14:59

标签: c# foreach generator enumerator

我来自Python世界,我试图在C#中创建一个“生成器”方法。我正在使用特定缓冲区大小的块解析文件,并且只希望一次读取和存储下一个块,并在foreach循环中生成它。这是我到目前为止(简化的概念证明):

class Page
{
    public uint StartOffset { get; set; }
    private uint currentOffset = 0;

    public Page(MyClass c, uint pageNumber)
    {
        uint StartOffset = pageNumber * c.myPageSize;

        if (StartOffset < c.myLength)
            currentOffset = StartOffset;
        else
            throw new ArgumentOutOfRangeException("Page offset exceeds end of file");

        while (currentOffset < c.myLength && currentOffset < (StartOffset + c.myPageSize))
            // read data from page and populate members (not shown for MWE purposes)
            . . .
    }
}

class MyClass
{
    public uint myLength { get; set; }
    public uint myPageSize { get; set; }

    public IEnumerator<Page> GetEnumerator()
    {
        for (uint i = 1; i < this.myLength; i++)
        {
            // start count at 1 to skip first page
            Page p = new Page(this, i);
            try
            {
                yield return p;
            }
            catch (ArgumentOutOfRangeException)
            {
                // end of available pages, how to signal calling foreach loop?
            }
        }
    }
}

我知道这不是完美的,因为它是一个最小的工作示例(我不允许公开设置许多这些属性,但为了保持这个简单,我不想键入私有成员和属性)。

然而,我的主要问题是如何让调用者使用foreach语句循环遍历MyClass,知道没有更多的项目需要循环?我抛出是否有异常表明没有剩余元素?

3 个答案:

答案 0 :(得分:1)

执行此操作的两种方法是让代码执行到达GetEnumerator函数的末尾或在代码中放入yield break;,这与函数中的return;的行为相同已退回void

从来电者的观察中,GetEnumerator()返回的枚举器将开始返回false MoveNext(),这就是他们告诉枚举器完成的方式。

要修复“无法使用catch子句在try块的主体内生成值”,将try / catch放在代码的错误部分周围,execption将被抛出{{1}不是new。您的代码应该是

yield return

答案 1 :(得分:1)

如评论中所述,您应使用IEnumerable<T>代替IEnumerator<T>。枚举器是用于枚举某些东西的技术对象。在许多情况下,这个东西是可以枚举的。

C#具有处理枚举的特殊能力。最突出的是,你可以使用带有可枚举的foreach循环(但不是枚举器;即使循环实际上使用了枚举的枚举器)。此外,enumerables允许您使用LINQ,这使得它更容易消费。

所以你应该像这样改变你的课程:

class MyClass
{
    public uint myLength { get; set; }
    public uint myPageSize { get; set; }

    # note the modified signature
    public IEnumerable<Page> GetPages()
    {
        for (uint i = 1; i < this.myLength; i++)
        {
            Page p;
            try
            {
                p = new Page(this, i);
            }
            catch (ArgumentOutOfRangeException)
            {
                yield break;
            }
            yield return p;
        }
    }
}

最后,这允许你像这样使用它:

var obj = new MyClass();

foreach (var page in obj.GetPages())
{
    // do whatever
}

// or even using LINQ
var pageOffsets = obj.GetPages().Select(p => p.currentOffset).ToList();

当然,您还应该将方法的名称更改为有意义的内容。如果您要返回页面,GetPages可能是朝着正确方向迈出的良好开端。名称GetEnumerator是为实现IEnumerable的类型保留的,其中GetEnumerator方法应返回对象所代表的集合的枚举器。

答案 2 :(得分:0)

使用yield break;语句结束迭代器方法生成的序列。