Implementing IEnumerable


Hot off the presses… What do you think?


 ..brad


style="FONT-FAMILY: Verdana; mso-ansi-language: ES">Implementing IEnumerable


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Almost all collections expose some
way to enumerate elements it contains.  The IEnumerable interface
encapsulates this notion such that common facilities can be built that work on
any kind of collection.   These guidelines
style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">apply style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> to any type implementing
ICollection, IList or IDictionary as well. 


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> see the
style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">generics style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> guidelines in section x.y
(tbd)


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
Do see section 11 for more collections
guidelines


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> implement IEnumerable on any type
that can be enumerated


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">     
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> name the class
ItemTypeCollection for implementations that contain homogenous types.
Such as StringCollection, EmployeeCollection, etc


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
Do
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">explicitly implement
IEnumerable.GetEnumerator()


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> provide a GetEnumerator() method
that returns a nested public struct called “Enumerator”


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Rationale: the foreach language
construct will prefer calling the method named GetEnumerator (that follows the
enumerator pattern) rather than the interface method which means there will be
one less GC heap allocation each time the collection is enumerated. style="mso-spacerun: yes">     


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> explicitly implement the
IEnumertor.Current property on the Enumerator struct


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Do style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> provide a strongly typed Current
property that returns the item type
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">on the Enumerator
struct


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Rationale: If item type is a value
type this avoids a boxing operation each time through the loop.  If item
type is a reference type there is a small savings for not having to do the
cast.


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
Avoid style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">having members on your
collection be virtual unless extensibility is truly
required


Rationale: The
JIT is not
able to inline calls to virtual methods. style="mso-spacerun: yes">  


style="FONT-FAMILY: Arial">


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">These guidelines lead to this
pattern…

style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">

style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">public style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">class style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> 
ItemTypeCollection: IEnumerable style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">{ style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> style="mso-spacerun: yes">   style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">public style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">struct style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
Enumerator : IEnumerator style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> style="mso-spacerun: yes">   { style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">    style="mso-spacerun: yes">   style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">public style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
ItemType Current { style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">get style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> {… }
} style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> style="mso-spacerun: yes">      style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">object style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
IEnumerator.Current { style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">get style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'"> {
style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">return style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
Current; } } style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">       style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">public style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">bool style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
MoveNext() { … } style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">      … style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">   } style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">    style="FONT-WEIGHT: normal; COLOR: blue; FONT-FAMILY: 'Courier New'">public style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">
Enumerator GetEnumerator() { … } style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">   IEnumerator
IEnumerable.GetEnumerator() { … } style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">   … style="FONT-WEIGHT: normal; FONT-SIZE: 12pt; COLOR: windowtext; FONT-FAMILY: 'Times New Roman'">


style="FONT-WEIGHT: normal; COLOR: windowtext; FONT-FAMILY: 'Courier New'">}



style="BACKGROUND: #e9fdf3; MARGIN: 6pt 0in; mso-add-space: auto"> style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">Tradeoff: Implementing this
pattern involves having an extra public type (the Enumerator) and several extra
public methods that are really there just for infrastructure reasons. style="mso-spacerun: yes">  These types add to the perceived
complexity of the
style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">API style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana"> and must be
documented, tested, versioned, etc. 
As such this pattern should only be followed where performance is
paramount.  


style="FONT-FAMILY: Arial">


style="FONT-FAMILY: 'Wingdings 2'; mso-fareast-font-family: 'Wingdings 2'; mso-bidi-font-family: 'Wingdings 2'">? style="FONT-SIZE: 7pt; mso-fareast-font-family: 'Wingdings 2'">    
Prefer style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">implementing IEnumerable
interface with optimistic concurrency


Rationale: style="mso-spacerun: yes">  There are two legitimate ways to
implement the IEnumerable interface. 
The first is to assume that the collection will not be modified while it
is being enumerated.  If it is
modified throw an InvalidOperationException exception. style="mso-spacerun: yes">  The 2nd way is to make a snap-shot of
the collection in the enumerator thus isolating the enumerator from changes in
the underlying collection.  For most
usages for enumerable.  For most
general usage scenarios the optimistic 
concurrency model provides net better performance.


style="MARGIN: 0in 0in 0pt">

Comments (6)

  1. I’d need to see the generics guidelines. In fact, why don’t you just send me all of you generic CLR bits right away, and I’ll give you more accurate feedback…

    Like the suggestion to use a struct instead of a class.

    But I don’t agree with optimistic concurrency for enumerators. Of course it’s faster. But it’s also silently broken in {some|many} scenarios.

    Also suggest you demo a pattern for non-optimistic scenarios that illustrates how the enumerator should depend on the collection, and detect changes via a concurrency counter or similar mechanism, not the other way around. If the collection tries to maintain references to enumerators it hands out then you get bad heap perf, since those enumerator instances never get GC’d until the collection goes away. Using a struct implies that you’re not going to explicity maintain a ref, but it would be nice to show an example.

  2. Just realized that I misread the last bit about optimistic enumeration. I originally thought the suggestion was to ignore changes in the collection – my bad.

  3. Frank Hileman says:

    Using a struct enumerator is a nice idea. But if you put too much stuff in it (to detect changes to the collection, for example) it starts getting fat.

    I never thought added the strong and weakly typed enumerator stuff was much work in design or testing. It all becomes boilerplate after a while.

  4. Keith Hill says:

    Regarding throwing InvalidOperationException, how can you tell when the collection is being enumerated or not?