C# "Generator" 方法
C# "Generator" Method
我来自 Python 的世界,我正在尝试用 C# 创建一个 "generator" 方法。我正在以特定缓冲区大小的块解析文件,并且只想一次读取和存储下一个块并在 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 的调用者知道没有更多的项目可以循环通过?我是否抛出异常以指示没有剩余元素?
使用 yield break;
语句结束迭代器方法生成的序列。
两种方法是让代码执行到 GetEnumerator 函数的末尾或在代码中放入一个 yield break;
,这与在一个 return;
中的行为相同返回 void
.
的函数
从调用者的角度来看,从 GetEnumerator()
返回的枚举器将开始为 MoveNext()
返回 false
,这就是他们告诉枚举器已完成的方式。
为了修复你的 "Can't yield a value inside the body of a try block with a catch clause",你将 try/catch 放在代码的错误部分,执行将在 new
而不是 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;
}
}
如评论中所述,您应该使用 IEnumerable<T>
而不是 IEnumerator<T>
。枚举器是用于枚举某物的技术对象。在许多情况下,某些东西是可枚举的。
C# 具有处理枚举的特殊能力。最突出的是,您可以将 foreach
循环与可枚举项一起使用(但不能使用枚举数;即使该循环实际上使用可枚举项的枚举数)。此外,枚举允许您使用 LINQ 这使得它更容易使用。
所以你应该像这样改变你的class:
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();
当然,你也应该把方法的name改成有意义的。如果您正在 return 页,GetPages
可能是朝着正确方向迈出的良好第一步。名称 GetEnumerator
是为实现 IEnumerable
的类型保留的,其中 GetEnumerator
方法应该 return 对象表示的集合的枚举数。
我来自 Python 的世界,我正在尝试用 C# 创建一个 "generator" 方法。我正在以特定缓冲区大小的块解析文件,并且只想一次读取和存储下一个块并在 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 的调用者知道没有更多的项目可以循环通过?我是否抛出异常以指示没有剩余元素?
使用 yield break;
语句结束迭代器方法生成的序列。
两种方法是让代码执行到 GetEnumerator 函数的末尾或在代码中放入一个 yield break;
,这与在一个 return;
中的行为相同返回 void
.
从调用者的角度来看,从 GetEnumerator()
返回的枚举器将开始为 MoveNext()
返回 false
,这就是他们告诉枚举器已完成的方式。
为了修复你的 "Can't yield a value inside the body of a try block with a catch clause",你将 try/catch 放在代码的错误部分,执行将在 new
而不是 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;
}
}
如评论中所述,您应该使用 IEnumerable<T>
而不是 IEnumerator<T>
。枚举器是用于枚举某物的技术对象。在许多情况下,某些东西是可枚举的。
C# 具有处理枚举的特殊能力。最突出的是,您可以将 foreach
循环与可枚举项一起使用(但不能使用枚举数;即使该循环实际上使用可枚举项的枚举数)。此外,枚举允许您使用 LINQ 这使得它更容易使用。
所以你应该像这样改变你的class:
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();
当然,你也应该把方法的name改成有意义的。如果您正在 return 页,GetPages
可能是朝着正确方向迈出的良好第一步。名称 GetEnumerator
是为实现 IEnumerable
的类型保留的,其中 GetEnumerator
方法应该 return 对象表示的集合的枚举数。