自定义枚举器






4.25/5 (7投票s)
修改任何枚举器的行为,使其成为循环、受限或步进的枚举器。 另有可逆枚举器。
引言
我喜欢使用 .NET Framework 的枚举器功能,特别是 foreach
关键字。代码看起来非常干净,易于理解和维护。但是,枚举器缺少一些所需的功能,例如
- 循环
- 可逆性
- 约束
- 步进
我已经创建了一个帮助类,它增强了对现有集合枚举器的枚举。由于 .NET 的 System.Collections
和 System.Collections.Generic
命名空间中有大量集合,并且具有适当的枚举器,即使开发人员也可以使用自定义枚举器创建他/她自己的集合类,解决方案必须指向修改这些枚举器的行为,而不是为每种集合创建自定义枚举器。
Helper 类
该帮助类包装了一组 static
方法,无需实例化对象即可使用。它们定义在 System.Collections
命名空间中,因此您不必担心在源文件中使用额外的命名空间。
namespace System.Collections
{
public class Enumerators
{
public static IEnumerable CircularEnum(IEnumerable _enumerator) ...
public static IEnumerable ReverseEnum(IEnumerable _enumerator) ...
public static IEnumerable SteppedEnum(IEnumerable _enumerator, int _step) ...
public static IEnumerable ConstrainedEnum
(IEnumerable _enumerator, int _start) ...
public static IEnumerable ConstrainedEnum
(IEnumerable _enumerator, int _start, int _count) ...
}
}
示例集合
正如在前面的代码中可以读到的,所有枚举器方法都将接收另一个枚举器对象(任何从 IEnumerable
接口派生的对象)。为了测试目的,我包含了一个 SortedList
泛型集合,该集合可以按键/值对或单独按键或值列表进行枚举。
SortedList<int,string> list = new SortedList<int,string>();
list.Add(1, "one");
list.Add(2, "two");
list.Add(3, "three");
list.Add(4, "four");
list.Add(5, "five");
list.Add(6, "six");
list.Add(7, "seven");
list.Add(8, "eight");
list.Add(9, "nine");
list.Add(10, "ten");
循环枚举器
最简单的枚举器是循环枚举器,由 CircularEnum
方法处理;它将在无限的 while
循环内调用原始枚举器,如下所示
public static IEnumerable CircularEnum(IEnumerable _enumerator)
{
while (true)
{
IEnumerator enu = _enumerator.GetEnumerator();
while (enu.MoveNext())
{
yield return enu.Current;
}
}
}
应该有一些控制机制来在特定条件下停止无限循环。在示例代码中,定义了一个常量,如下所示
const int max = 15; // Max number of iterations to stop circular enumerator
定义了停止行为后,循环枚举器可以这样使用
Console.WriteLine("Dictionary circular enumeration:");
int i = 0;
foreach (KeyValuePair<int, string> pair in Enumerators.CircularEnum(list))
{
Console.WriteLine(" " + pair.ToString());
if (++i >= max)
break; // stop circular enumerator, will be infinite if not
}
Console.WriteLine(" (stopped)\r\n");
输出
Dictionary circular enumeration:
[1, one]
[2, two]
[3, three]
[4, four]
[5, five]
[6, six]
[7, seven]
[8, eight]
[9, nine]
[10, ten]
[1, one]
[2, two]
[3, three]
[4, four]
[5, five]
(stopped)
受限枚举器
有时需要遍历枚举集合的一部分,类似于 Array.Copy()
方法。ConstrainedEnum
方法有两个版本,允许指定起始元素和可选的元素计数。第一个元素的索引为零,计数可以为零或更大。
public static IEnumerable ConstrainedEnum(IEnumerable _enumerator, int _start)
{
if (_start < 0)
throw new ArgumentException
("Invalid step value, must be positive or zero.");
IEnumerator enu = _enumerator.GetEnumerator();
while (enu.MoveNext())
{
if (--_start < 0)
yield return enu.Current;
}
}
public static IEnumerable ConstrainedEnum
(IEnumerable _enumerator, int _start, int _count)
{
if (_start < 0)
throw new ArgumentException
("Invalid step value, must be positive or zero.");
if (_count < 0)
throw new ArgumentException
("Invalid count value, must be positive or zero.");
if (_count > 0)
{
IEnumerator enu = _enumerator.GetEnumerator();
if (enu.MoveNext())
{
while (--_start > 0)
{
if (!enu.MoveNext())
break;
}
if (_start <= 0)
{
while (--_count >= 0)
{
if (enu.MoveNext())
yield return enu.Current;
else
break;
}
}
}
}
}
示例代码使用 ConstrainedEnum
方法的第二个版本
Console.WriteLine("Constrained enumeration (2,5):");
foreach (KeyValuePair<int,> pair in Enumerators.ConstrainedEnum(list, 2, 5))
{
Console.WriteLine(" " + pair.ToString());
}
Console.WriteLine(" (finished)\r\n");
输出
Constrained enumeration (2,5):
[3, three]
[4, four]
[5, five]
[6, six]
[7, seven]
(finished)
步进枚举器
此方法 (SteppedEnum
) 允许遍历一个集合,跳过某些元素。使用步进值 1 将像常规枚举器一样运行,步进值 2 将在每次迭代中跳过一个元素,依此类推。
public static IEnumerable SteppedEnum(IEnumerable _enumerator, int _step)
{
if (_step < 1)
throw new ArgumentException
("Invalid step value, must be greater than zero.");
IEnumerator enu = _enumerator.GetEnumerator();
while (enu.MoveNext())
{
yield return enu.Current;
for (int i = _step; i > 1; i--)
if (!enu.MoveNext())
break;
}
}
示例代码将枚举一个集合,在每次迭代中跳过两个元素
Console.WriteLine("Stepped enumeration (3):");
foreach (int value in Enumerators.SteppedEnum(list.Keys, 3))
{
Console.WriteLine(" " + value.ToString());
}
Console.WriteLine(" (finished)\r\n");
输出
Stepped enumeration (3):
1
4
7
10
(finished)
奖励:反向枚举器
.NET 枚举器(从 IEnumerable
派生)并未设计为向后遍历,因此,理论上,不能存在反向枚举器。ReverseEnum
方法进行一些反射处理,以在由传递的枚举器对象表示的集合中查找索引器属性。反向枚举过程将通过使用其索引访问集合中的每个元素来实现。如果集合没有 Count
和 Item
属性,该方法将抛出异常,因此集合应从 IList 或 IList<> 接口派生。
public static IEnumerable ReverseEnum(IEnumerable _enumerator)
{
System.Reflection.PropertyInfo countprop =
_enumerator.GetType().GetProperty("Count", typeof(int));
if (countprop == null)
throw new ArgumentException
("Collection doesn't have a Count property, cannot enumerate.");
int count = (int)countprop.GetValue(_enumerator, null);
if (count<1)
throw new ArgumentException("Collection is empty");
System.Reflection.PropertyInfo indexer =
_enumerator.GetType().GetProperty("Item", new Type[] { typeof(int) });
if (indexer == null)
throw new ArgumentException
("Collection doesn't have a proper indexed property, cannot enumerate.");
for (int i = count - 1; i >= 0; i--)
yield return indexer.GetValue(_enumerator, new object[] { i });
}
对于此方法,示例代码将仅遍历 Values 集合,而不是键/值对
Console.WriteLine("Reverse enumeration:");
foreach (string value in Enumerators.ReverseEnum(list.Values))
{
Console.WriteLine(" " + value.ToString());
}
Console.WriteLine(" (finished)\r\n");
输出
Reverse enumeration:
ten
nine
eight
seven
six
five
four
three
two
one
(finished)
组合枚举器
枚举器可以以任何方式组合(链接),但您应该注意产生的效果。它与循环步进枚举器或步进循环枚举器不同。示例代码在一个 foreach
语句中组合了所有四种枚举器
Console.WriteLine
("Combined Constrained(2,6)-Stepped(3)-Circular-Reverse enumeration:");
i = 0;
foreach (int value in Enumerators.ConstrainedEnum
(Enumerators.SteppedEnum(Enumerators.CircularEnum
(Enumerators.ReverseEnum(list.Keys)), 3), 2, 6))
{
Console.WriteLine(" " + value.ToString());
if (++i >= max * 2)
break; // stop circular enumerator, will be infinite if not
}
Console.WriteLine(" (finished)");
输出
Combined Constrained(2,6)-Stepped(3)-Circular-Reverse enumeration:
4
1
8
5
2
9
(finished)
使用源代码
提供的源代码是使用 Visual C# 2008 构建的,因此尝试在较低版本中编译解决方案可能不可行。无论如何,您可以简单地创建一个新的控制台项目并附加 Program.cs 和 Enumerators.cs 源文件。要在您自己的项目中使用 Enumerators
类,您只需要最后一个文件。
历史
- 2008 年 8 月 30 日:第一个版本