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

发布于 2024-12-14 04:10:51 字数 581 浏览 2 评论 0 原文

我有一个名为 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

白昼 2024-12-21 04:10:51

通过使用 IEnumerableIEnumerator 的通用版本来减少您的困惑(?)。

可枚举的排列是IEnumerable>。因此,您可能会遇到类似的情况

IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence)
{
    return new Permuter<T>(sequence);
}

public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... }

而且,我见过不止一种情况,其中单个类型同时实现了 IEnumerableIEnumerator;它的 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();
    }
}

Reduce your confusion (?) by using the generic versions of IEnumerable and IEnumerator.

A permutation enumerable is IEnumerable<IEnumerable<T>>. So you might have something like

IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence)
{
    return new Permuter<T>(sequence);
}

and

public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... }

Furthermore, I've seen more than one case where a single type implemented both IEnumerable<T> and IEnumerator<T>; its GetEnumerator method was simply return 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

var permuter = GetPermutations(sequence);
foreach (var permutation in permuter)
{
    foreach (var item in permutation)
        Console.Write(item + "; ");
    Console.WriteLine();
}

Assuming the input sequence is { 1, 2, 3 }, the output is

1; 2; 3; 
1; 3; 2; 
2; 1; 3; 
2; 3; 1; 
3; 1; 2; 
3; 2; 1; 

EDIT:

Here's a super-inefficient implementation to illustrate the suggestion:

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();
    }
}
少女情怀诗 2024-12-21 04:10:51

一个对象可以同时充当 IEnumeratorIEnumerable,但对象通常很难以如下方式执行此操作:避免奇怪的语义;除非 IEnumerator 将是无状态的(例如,空枚举器,其中 MoveNext() 始终返回 false,或者无限重复枚举器,其中 MoveNext() 不执行任何操作,但始终返回 true,并且 Current 始终返回相同的值),每次调用 GetEnumerator() 都必须返回一个不同的对象实例,并且让该实例实现 IEnumerable 可能没有什么价值。

让值类型实现 IEnumerableIEnumerator,并使其 GetEnumerator() 方法返回 this,将满足每次调用 GetEnumerator 返回一个不同的对象实例的要求,但让值类型实现可变接口通常是危险的。如果将值类型装箱到 IEnuerator 并且从未拆箱,则它将表现为类类型对象,但没有真正的理由说明为什么它不应该只是类类型对象。

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

It is possible for one object to behave as both an IEnumerator<T> and IEnumerable<T>, but it is generally difficult for an object to do so in such fashion as to avoid quirky semantics; unless the IEnumerator<T> is going to be stateless (e.g. an empty enumerator, where MoveNext() always returns false, or an endless-repeat enumerator, where MoveNext() does nothing but always returns true, and Current always returns the same value), every call to GetEnumerator() must return a distinct object instance, and there's likely to be little value in having that instance implement IEnumerable<T>.

Having a value type implement IEnumerable<T> and IEnumerator<T>, and having its GetEnumerator() method return this, would satisfy the requirement that each call to GetEnumerator return a distinct object instance, but having value types implement mutable interfaces is generally dangerous. If a value type is boxed to IEnuerator<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> and IEnumerator<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 their IDisposable behavior. I would not recommend this approach in any code that needs to be human-readable; since the IEnumerator<T> and IEnumerable<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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文