我有一个名为GenericPermutations的类,它既是可枚举的又是枚举器。它的工作是获取一个有序的对象列表,并按顺序迭代它们的每个排列。
示例,此类的整数实现可以遍历以下内容:
GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 });
p.nextPermutation(); // 123
p.nextPermutation(); // 132
p.nextPermutation(); // 213
// etc.
所以它的枚举在某种意义上说它包含了你可以枚举的事物的“列表”。它也是一个普查员,因为它的工作涉及寻找下一个排列。
问题:我目前正在尝试将IEnumerator和IEnumerable与此类集成,在我看来它应该是两者(而不是使用子类作为IEnumerable)。到目前为止,我已经避免了通过在GetEnumerator
方法中传递新的GenericPermutation对象来尝试从中获取两个枚举器的问题。
这是个坏主意吗?还有什么我应该考虑的吗?
答案 0 :(得分:9)
使用IEnumerable
和IEnumerator
的通用版本减少您的混淆(?)。
可枚举的排列是IEnumerable<IEnumerable<T>>
。所以你可能会有像
IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence)
{
return new Permuter<T>(sequence);
}
和
public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... }
此外,我看过多个案例,其中单个类型同时实施了IEnumerable<T>
和IEnumerator<T>
;它的GetEnumerator方法只是return this;
。
我认为这样的类型需要是一个结构体,因为如果它是一个类,如果在第一次枚举完成之前第二次调用GetEnumerator(),则会遇到各种各样的问题。
编辑:使用permuter
var permuter = GetPermutations(sequence);
foreach (var permutation in permuter)
{
foreach (var item in permutation)
Console.Write(item + "; ");
Console.WriteLine();
}
假设输入序列为{1,2,3},则输出为
1; 2; 3;
1; 3; 2;
2; 1; 3;
2; 3; 1;
3; 1; 2;
3; 2; 1;
编辑:
这是一个超低效的实现来说明这个建议:
public class Permuter<T> : IEnumerable<IEnumerable<T>>
{
private readonly IEnumerable<T> _sequence;
public Permuter(IEnumerable<T> sequence)
{
_sequence = sequence;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
foreach(var item in _sequence)
{
var remaining = _sequence.Except(Enumerable.Repeat(item, 1));
foreach (var permutation in new Permuter<T>(remaining))
yield return Enumerable.Repeat(item, 1).Concat(permutation);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
答案 1 :(得分:1)
一个对象可能同时表现为IEnumerator<T>
和IEnumerable<T>
,但对象通常很难以避免古怪语义的方式这样做;除非IEnumerator<T>
将成为无状态(例如空的枚举器,其中MoveNext()
总是返回false,或者是无限重复的枚举器,其中MoveNext()
什么都不做但总是返回true,{ {1}}总是返回相同的值),每次调用Current
都必须返回一个不同的对象实例,而且实例GetEnumerator()
实现的价值可能很小。
拥有值类型工具IEnumerable<T>
和IEnumerable<T>
,并使其IEnumerator<T>
方法返回GetEnumerator()
,将满足每次调用this
的要求一个独特的对象实例,但具有值类型实现可变接口通常是危险的。如果值类型被装箱到GetEnumerator
并且从未取消装箱,它将表现为类型对象,但没有真正的理由说明为什么它不应该只是一个类型对象。
C#中的迭代器被实现为实现IEnuerator<T>
和IEnumerable<T>
的类对象,但它们包含一些花哨的逻辑以确保语义正确性。实际效果是,让一个对象实现两个接口可以略微提高性能,换取生成代码中的相当复杂性,以及IEnumerator<T>
行为中的一些语义怪癖。我不会在任何需要人类可读的代码中推荐这种方法;因为类的IDisposable
和IEnumerator<T>
方面主要使用不同的字段,并且因为组合类需要具有“thread-id”字段,如果使用单独的类则不需要该字段,性能通过使用相同的对象实现两个接口可以实现的改进是有限的。值得一提的是,如果将复杂性添加到编译器中,将为数百万个迭代器例程提供轻微的性能提升,但不值得提高一个例程的性能。