【问题标题】:C# "Generator" MethodC#“生成器”方法
【发布时间】:2016-11-11 11:58:36
【问题描述】:

我来自 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 的调用者知道没有更多的项目可以循环了?有没有我抛出的异常表明没有剩余元素?

【问题讨论】:

  • 您只需停止生成项目,就像在 Python 中一样。话虽如此,您应该创建一个返回IEnumerable&lt;BTreePage&gt;的方法;枚举更容易使用。
  • IEnumerator&lt;T&gt;.MoveNext 告诉调用者停止迭代。这是在您使用yield return 时为您实现的。如果您想明确停止,可以使用yield break
  • @poke 在示例中不一致是我的错。 Page 是这篇文章的虚构内容,BTreePage 确实是我在真实代码中返回的内容。固定。
  • 我在评论使用 IEnumerable&lt;T&gt;IEnumerator&lt;T&gt;
  • @Dan 请参阅this question。基本上(因为您来自 Python),IEnumerable 是生成器或列表,而 IEnumerator 将是您在其上调用 iter() 时得到的东西(技术上的东西)。大多数时候,您会想要IEnumerable,因为它更容易使用(例如,使用foreach 循环)。

标签: c# foreach generator enumerator


【解决方案1】:

如 cmets 中所述,您应该使用 IEnumerable&lt;T&gt; 而不是 IEnumerator&lt;T&gt;。枚举器是用于枚举某物的技术对象。那个东西——在很多情况下——是可枚举的。

C# 具有处理枚举的特殊能力。最突出的是,您可以将foreach 循环与可枚举(但不是枚举器;即使循环实际上使用可枚举的枚举器)一起使用。此外,可枚举允许您使用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】:

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

从调用者的角度来看,从GetEnumerator() 返回的枚举器将开始为MoveNext() 返回false,这就是他们告诉枚举器完成的方式。


要修复您的“无法在带有 catch 子句的 try 块的主体内产生值”,您将 try/catch 放在代码的错误部分,执行将被抛出 new not yield return。你的代码应该是这样的

public IEnumerator<Page> GetEnumerator()
{
    for (uint i = 1; i < this.myLength; i++)
    {
        // start count at 1 to skip first page
        Page p;
        try
        {
            p = new Page(this, i);
        }
        catch (ArgumentOutOfRangeException)
        {
            yield break;
        }
        yield return p;
    }
}

【讨论】:

  • 这是正确的答案,但现在我有两个问题;)——显然我不能在 try 语句中使用 yield。精氨酸
  • 奇数。我得到“无法在带有 catch 子句的 try 块的主体内产生值”
  • 是的,you can’t do that。只需捕获变量中的值,并在 try/catch 之后产生它。
  • @Dan 用一个完整的例子更新了答案。无论如何,您应该尝试一下new,这就是异常发生的地方。
  • @Dan 请注意,使用ArgumentOutOfRangeException 是一个糟糕的设计选择。您不应该对正常的程序流控制使用异常。更好的设计选择是在GetEnumerator 内进行范围数学运算,并且仅在它是有效范围值时才调用new Page
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2016-05-31
    • 2010-10-03
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多