65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义枚举器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (7投票s)

2008年8月30日

CPOL

3分钟阅读

viewsIcon

69748

downloadIcon

233

修改任何枚举器的行为,使其成为循环、受限或步进的枚举器。 另有可逆枚举器。

引言

我喜欢使用 .NET Framework 的枚举器功能,特别是 foreach 关键字。代码看起来非常干净,易于理解和维护。但是,枚举器缺少一些所需的功能,例如

  • 循环
  • 可逆性
  • 约束
  • 步进

我已经创建了一个帮助类,它增强了对现有集合枚举器的枚举。由于 .NET 的 System.CollectionsSystem.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 方法进行一些反射处理,以在由传递的枚举器对象表示的集合中查找索引器属性。反向枚举过程将通过使用其索引访问集合中的每个元素来实现。如果集合没有 CountItem 属性,该方法将抛出异常,因此集合应从 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.csEnumerators.cs 源文件。要在您自己的项目中使用 Enumerators 类,您只需要最后一个文件。

历史

  • 2008 年 8 月 30 日:第一个版本
© . All rights reserved.