我有一个名为 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 对象来避免尝试从中获取两个枚举器的问题。
这是一个坏主意吗?我还应该考虑什么吗?
I have a class called GenericPermutations that is both enumerable and an enumerator. Its job is to take an ordered list of objects and iterate through each permutation of them in order.
Example, an integer implemenation of this class could iterate through the following:
GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 });
p.nextPermutation(); // 123
p.nextPermutation(); // 132
p.nextPermutation(); // 213
// etc.
So its enumerable in the sense that it contains a 'list' of things you can enumerate over. It's also an enumerator, because its job involves finding the next permutation.
THE ISSUE: I am currently trying to integrate IEnumerator and IEnumerable with this class, and it seems to me like it should be both (rather than using a sub class as the IEnumerable). Thus far I have avoided the issue with trying to get two enumerators from it by passing a new GenericPermutation object in the GetEnumerator
method.
Is this a bad idea? Anything else I should consider?
发布评论
评论(2)
通过使用
IEnumerable
和IEnumerator
的通用版本来减少您的困惑(?)。可枚举的排列是
IEnumerable>
。因此,您可能会遇到类似的情况,
而且,我见过不止一种情况,其中单个类型同时实现了
IEnumerable
和IEnumerator
;它的 GetEnumerator 方法只是return this;
。不过,我认为这样的类型需要是一个结构体,因为如果它是一个类,如果在第一次枚举完成之前第二次调用 GetEnumerator() ,就会遇到各种各样的问题。
编辑:使用排列器
假设输入序列是 { 1, 2, 3 },输出是
编辑:
这是一个超级低效的实现来说明建议:
Reduce your confusion (?) by using the generic versions of
IEnumerable
andIEnumerator
.A permutation enumerable is
IEnumerable<IEnumerable<T>>
. So you might have something likeand
Furthermore, I've seen more than one case where a single type implemented both
IEnumerable<T>
andIEnumerator<T>
; its GetEnumerator method was simplyreturn this;
.I think such a type would need to be a struct, though, because if it were a class you'd have all sorts of problems if you called GetEnumerator() a second time before the first enumeration was completed.
EDIT: Consuming the permuter
Assuming the input sequence is { 1, 2, 3 }, the output is
EDIT:
Here's a super-inefficient implementation to illustrate the suggestion:
一个对象可以同时充当 可能没有什么价值。
IEnumerator
和IEnumerable
,但对象通常很难以如下方式执行此操作:避免奇怪的语义;除非IEnumerator
将是无状态的(例如,空枚举器,其中MoveNext()
始终返回 false,或者无限重复枚举器,其中MoveNext()
不执行任何操作,但始终返回 true,并且Current
始终返回相同的值),每次调用GetEnumerator()
都必须返回一个不同的对象实例,并且让该实例实现 IEnumerable让值类型实现
IEnumerable
和IEnumerator
,并使其GetEnumerator()
方法返回this,将满足每次调用
并且从未拆箱,则它将表现为类类型对象,但没有真正的理由说明为什么它不应该只是类类型对象。GetEnumerator
返回一个不同的对象实例的要求,但让值类型实现可变接口通常是危险的。如果将值类型装箱到 IEnueratorC# 中的迭代器被实现为同时实现
IEnumerable
和IEnumerator
的类对象,但它们包含相当多的奇特逻辑以确保语义正确性。最终效果是,让一个对象实现这两个接口可以稍微提高性能,但代价是生成的代码相当复杂,并且其IDisposable
行为中存在一些语义上的怪异。我不会在任何需要人类可读的代码中推荐这种方法;因为类的IEnumerator
和IEnumerable
方面大多使用不同的字段,并且由于组合类需要有一个“thread-id”字段,如果使用单独的类则不需要,通过使用相同的对象来实现两个接口所实现的性能改进是有限的。如果增加编译器的复杂性将为数百万个迭代器例程提供轻微的性能改进,那么也许值得做,但不值得为提高一个例程的性能而做。It is possible for one object to behave as both an
IEnumerator<T>
andIEnumerable<T>
, but it is generally difficult for an object to do so in such fashion as to avoid quirky semantics; unless theIEnumerator<T>
is going to be stateless (e.g. an empty enumerator, whereMoveNext()
always returns false, or an endless-repeat enumerator, whereMoveNext()
does nothing but always returns true, andCurrent
always returns the same value), every call toGetEnumerator()
must return a distinct object instance, and there's likely to be little value in having that instance implementIEnumerable<T>
.Having a value type implement
IEnumerable<T>
andIEnumerator<T>
, and having itsGetEnumerator()
method returnthis
, would satisfy the requirement that each call toGetEnumerator
return a distinct object instance, but having value types implement mutable interfaces is generally dangerous. If a value type is boxed toIEnuerator<T>
and never unboxed, it will behave as a class-type object, but there's no real reason why it shouldn't have simply been a class-type object.Iterators in C# are implemented as class objects which implement both
IEnumerable<T>
andIEnumerator<T>
, but they include a fair bit of fancy logic to ensure semantic correctness. The net effect is that having one object implement both interfaces offers a slight improvement in performance, in exchange for a fair bit of complexity in the generated code, and some semantic quirkiness in theirIDisposable
behavior. I would not recommend this approach in any code that needs to be human-readable; since theIEnumerator<T>
andIEnumerable<T>
aspects of the class mostly use different fields, and since a combined class needs to have a "thread-id" field which wouldn't be needed if using separate classes, the performance improvement one can achieve by using the same object to implement for both interfaces is limited. Worth doing perhaps if adding the complexity to the compiler will provide that slight performance improvement to millions of iterator routines, but not worth doing to improve the performance of one routine.