C#Class同时是IEnumerable和IEnumerator。这有什么问题?

时间:2011-11-08 22:39:44

标签: c# ienumerable enumeration ienumerator

我有一个名为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对象来尝试从中获取两个枚举器的问题。

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

2 个答案:

答案 0 :(得分:9)

使用IEnumerableIEnumerator的通用版本减少您的混淆(?)。

可枚举的排列是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>行为中的一些语义怪癖。我不会在任何需要人类可读的代码中推荐这种方法;因为类的IDisposableIEnumerator<T>方面主要使用不同的字段,并且因为组合类需要具有“thread-id”字段,如果使用单独的类则不需要该字段,性能通过使用相同的对象实现两个接口可以实现的改进是有限的。值得一提的是,如果将复杂性添加到编译器中,将为数百万个迭代器例程提供轻微的性能提升,但不值得提高一个例程的性能。