7

我有一个名为 GenericPermutations 的类,它既是可枚举的,又是枚举器。它的工作是获取一个有序的对象列表,并按顺序遍历它们的每个排列。

例如,此类的整数实现可以遍历以下内容:

GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 });
p.nextPermutation(); // 123
p.nextPermutation(); // 132
p.nextPermutation(); // 213
// etc.

所以它是可枚举的,因为它包含一个你可以枚举的东西的“列表”。它也是一个枚举器,因为它的工作涉及找到下一个排列。

问题:我目前正在尝试将 IEnumerator 和 IEnumerable 与此类集成,在我看来它应该两者兼而有之(而不是使用子类作为 IEnumerable)。到目前为止,我已经避免了尝试通过在方法中传递一个新的 GenericPermutation 对象来从中获取两个枚举数的问题GetEnumerator

这是一个坏主意吗?还有什么我应该考虑的吗?

4

2 回答 2

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>and IEnumerator<T>; 它的 GetEnumerator 方法很简单return this;

不过,我认为这样的类型需要是一个结构,因为如果它是一个类,如果在第一次枚举完成之前第二次调用 GetEnumerator(),就会遇到各种问题。

编辑:消费置换器

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();
    }
}
于 2011-11-08T22:45:23.177 回答
2

一个对象可以同时表现为一个IEnumerator<T>and IEnumerable<T>,但一个对象通常很难以避免古怪语义的方式这样做;除非将IEnumerator<T>是无状态的(例如,一个空的枚举器,它MoveNext()总是返回 false,或者一个无限重复的枚举器,MoveNext()它什么都不做,但总是返回 true,并且Current总是返回相同的值),每次调用都GetEnumerator()必须返回一个不同的对象实例,并且实现该实例的价值可能很小IEnumerable<T>

拥有一个值类型实现IEnumerable<T>andIEnumerator<T>并拥有它的GetEnumerator()方法 returnthis将满足每次调用GetEnumerator返回一个不同的对象实例的要求,但是让值类型实现可变接口通常是危险的。如果一个值类型被装箱IEnuerator<T>并且从不拆箱,它将表现为一个类类型对象,但没有真正的理由为什么它不应该只是一个类类型对象。

IEnumerable<T>C# 中的迭代器被实现为实现和的类对象IEnumerator<T>,但它们包含一些花哨的逻辑以确保语义正确性。最终的效果是,让一个对象实现两个接口可以稍微提高性能,以换取生成的代码相当复杂,并且它们的IDisposable行为有一些语义上的古怪。我不会在任何需要人类可读的代码中推荐这种方法。因为IEnumerator<T>IEnumerable<T>类的各个方面大多使用不同的字段,并且由于组合类需要有一个“thread-id”字段,如果使用单独的类则不需要,因此可以通过使用相同的对象来实现两者的性能改进接口有限。如果增加编译器的复杂性将为数百万个迭代器例程提供轻微的性能改进,那么也许值得这样做,但不值得为提高一个例程的性能而做。

于 2013-01-27T18:18:01.460 回答