Enumerator Implementation: Use struct or class?

2019-04-05 00:51发布

问题:

I noticed that List<T> defines its enumerator as a struct, while ArrayList defines its enumerator as a class. What's the difference? If I am to write an enumerator for my class, which one would be preferable?

EDIT: My requirements cannot be fulfilled using yield, so I'm implementing an enumerator of my own. That said, I wonder whether it would be better to follow the lines of List<T> and implement it as a struct.

回答1:

Like this others, I would choose a class. Mutable structs are nasty. (And as Jared suggests, I'd use an iterator block. Hand-coding an enumerator is fiddly to get right.)

See this thread for an example of the list enumerator being a mutable struct causing problems...



回答2:

The easiest way to write an enumerator in C# is with the "yield return" pattern. For example.

public IEnumerator<int> Example() {
  yield return 1;
  yield return 2;
}

This pattern will generate all of the enumerator code under the hood. This takes the decision out of your hands.



回答3:

Reason List uses a struct enumerator is to prevent garbage generation in foreach statements. This is pretty good reason especially if you are programming for Compact Framework, because CF doesn't have generational GC and CF is usually used on low performance hardware where it can quickly lead to performance issues.

Also, I don't think mutable structs are source of problems in examples some posted, but programmers that don't have good understanding of how value types work.



回答4:

An enumerator is inherently a changing structure, since it needs to update internal state to move on to the next value in the original collection.

In my opinion, structs should be immutable, so I would use a class.



回答5:

Write it using yield return.

As to why you might otherwise choose between class or struct, if you make it a struct then it gets boxed as soon as it is returned as an interface, so making it a struct just causes additional copying to take place. Can't see the point of that!



回答6:

Any implementation of IEnumerable<T> should return a class. It may be useful for performance reasons to have a GetEnumerator method which returns a struct which provides the methods necessary for enumeration but does not implement IEnumerator<T>; this method should be different from IEnumerable<T>.GetEnumerator, which should then be implemented explicitly.

Using this approach will allow for enhanced performance when the class is enumerated using a foreach or "For Each" loop in C# or vb.net or any context where the code which is doing the enumeration will know that the enumerator is a struct, but avoid the pitfalls that would otherwise occur when the enumerator gets boxed and passed by value.



回答7:

There's a couple of blog posts that cover exactly this issue. Basically, enumerator structs are a really really bad idea...



回答8:

To expand on @Earwicker: you're usually better off not writing an enumerator type, and instead using yield return to have the compiler write it for you. This is because there are a number of important subtleties that you might miss if you do it yourself.

See SO question "What is the yield keyword used for in C#?" for some more details on how to use it.

Also Raymond Chen has a series of blog posts ("The implementation of iterators in C# and its consequences": parts 1, 2, 3, and 4) that show you how to implement an iterator properly without yield return, which shows just how complex it is, and why you should just use yield return.