473,767 Members | 7,178 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

IEnumerable is strange

Dear mpdls,

here is a simple example of an IEnumerable that
generates integers:

It works, but I have only a vague idea of what's
going on. I understand that /yield/ wraps the
humble integer that comes from

counter++

into an IEnumerator<int >.

I don't understand why I have to implement TWO
GetEnumerator()-methods that both do the same and
only have different return types. Neither do I
understand which one is picked by /foreach/.

If I don't implement both GetEnumerator() s,
I get errors of omission.

IEnumerator is something like a /Generator/, right?
By the way, where is comp.lang.cshar p?

Thanks
Tin

using System;
using System.Collecti ons.Generic;
using System.Text;

namespace Riba
{
public class NumberEnumerabl e : IEnumerable<int >
{
private int counter = 0;
public IEnumerator<int > Core()
{

for (; ; )
{
yield return counter++;
}
}

System.Collecti ons.IEnumerator
System.Collecti ons.IEnumerable .GetEnumerator( )
{
return Core();
}

public IEnumerator<int > GetEnumerator()
{
return Core();
}
}
class Program
{
static void Main(string[] args)
{
foreach (int i in new NumberEnumerabl e())
{
System.Console. WriteLine("{0}" , i);
}
}
}
}

Jun 29 '06 #1
5 4202
Tin,

See inline:
"Tin Gherdanarra" <ti*********@gm ail.com> wrote in message
news:4g******** *****@individua l.net...
Dear mpdls,

here is a simple example of an IEnumerable that
generates integers:

It works, but I have only a vague idea of what's
going on. I understand that /yield/ wraps the
humble integer that comes from

counter++

into an IEnumerator<int >.

I don't understand why I have to implement TWO
GetEnumerator()-methods that both do the same and
only have different return types. Neither do I
understand which one is picked by /foreach/.
I agree with you on the frustration of having to implement two
GetEnumerator methods. The reason is because IEnumerable<T> derives from
IEnumerable. Because they both have one method (GetEnumerator) with the
same signatures, you have to have the strange implementation which requires
one explicit/one implicit, or two explicit ones.

If I don't implement both GetEnumerator() s,
I get errors of omission.

IEnumerator is something like a /Generator/, right?
By the way, where is comp.lang.cshar p?
I don't believe there is one.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard. caspershouse.co m

Thanks
Tin

using System;
using System.Collecti ons.Generic;
using System.Text;

namespace Riba
{
public class NumberEnumerabl e : IEnumerable<int >
{
private int counter = 0;
public IEnumerator<int > Core()
{

for (; ; )
{
yield return counter++;
}
}

System.Collecti ons.IEnumerator
System.Collecti ons.IEnumerable .GetEnumerator( )
{
return Core();
}

public IEnumerator<int > GetEnumerator()
{
return Core();
}
}
class Program
{
static void Main(string[] args)
{
foreach (int i in new NumberEnumerabl e())
{
System.Console. WriteLine("{0}" , i);
}
}
}
}

Jun 29 '06 #2
Tin Gherdanarra <ti*********@gm ail.com> wrote:
It works, but I have only a vague idea of what's
going on. I understand that /yield/ wraps the
humble integer that comes from

counter++

into an IEnumerator<int >.

I don't understand why I have to implement TWO
GetEnumerator()-methods that both do the same and
only have different return types.
It's for compatibility reasons, but it's also useful because covariance
of generic types isn't supported by the .NET framework. For example, if
you want to write a method which takes in an IEnumerable but don't want
or need the method to be generic, then you'd probably like to write
IEnumerable<obj ect> and have IEnumerable<int > convert to that type. That
doesn't work in C#. Instead, we've got the old IEnumerable.

It's easy to implement the IEnumerator version - simply return the
IEnumerator<T> version.
Neither do I
understand which one is picked by /foreach/.
If the type of the expression to the right of the 'in' statically(*)
supports IEnumerable<T>, and the iteration variable of the foreach is
assignment compatible with this type T, then the foreach will use the
statically typed version. If the type statically supports IEnumerable<T>
and the iteration variable is *not* assignment compatible, then an error
will occur. Otherwise, the type must statically support IEnumerable, and
a runtime cast will be generated for each iteration.

(*) Statically in this case refers to the *compile-time* type of the
expression to the right of the 'in' in the 'foreach' statement.
If I don't implement both GetEnumerator() s,
I get errors of omission.

IEnumerator is something like a /Generator/, right?
It's an implementation of CLU's iterator feature, but generator is
another name for it.
using System;
using System.Collecti ons.Generic;
using System.Text;

namespace Riba
{
public class NumberEnumerabl e : IEnumerable<int >
{
private int counter = 0;
public IEnumerator<int > Core()
{

for (; ; )
{
yield return counter++;
}
} Note that your counter variable can be local, it doesn't need to be a
field.
System.Collecti ons.IEnumerator
System.Collecti ons.IEnumerable .GetEnumerator( )
{
return Core();
You don't need to factor the Core() method at all. You can simply return
GetEnumerator() here and it will return the version returning
IEnumerator<int >, since that one isn't an explicit interface
implementation.
}

public IEnumerator<int > GetEnumerator()
{
return Core();

}
}

class Program
{
static void Main(string[] args)
{
foreach (int i in new NumberEnumerabl e())
{
System.Console. WriteLine("{0}" , i);
}
}
}
}


Since you're only using the class NumberEnumerabl e() to implement an
enumerator, you don't need to define a separate class at all. Iterators
can also return IEnumerable<T>:

---8<---
using System;
using System.Collecti ons.Generic;

class Program
{
static IEnumerable<int > NumberEnumerabl e()
{
int i = 0;
for (;;)
yield return i++;
}

static void Main(string[] args)
{
foreach (int i in NumberEnumerabl e())
{
System.Console. WriteLine("{0}" , i);
}
}
}
--->8---

-- Barry

--
http://barrkel.blogspot.com/
Jun 29 '06 #3
Thanks for the elaborate reply, but maybe IEnumerable
is the wrong thing here anyway. You reply:
IEnumerator is something like a /Generator/, right?

It's an implementation of CLU's iterator feature, but generator is
another name for it.


A big advantage of generators is that you can recycle
them and use them in the fashion of command-line pipes,
like this:

RandomNumberGen erator | FilterPrimeNumb ersGenerator | MultiplyBy4...

I assumed I can do the same with IEnumerable, but it
seems that it is a lot more tricky than I thought.

The key is that I (wrongly) guessed that /yield/ is just a mechanism
for marking the spot where a function returns this time
and will continue next time. /foreach/ simply calls the
function embodying the /yield/. This can't be true, I guess.
Here is an illustration:

int foo()
{
int counter = 0;
yield counter++;
}

Multiple clients can call foo, and the
spot where to continue has to be remembered for each
new caller. This means that the first call to /foo/
somehow instantiates a new environment for that
particular foo(), complete with its own version
of /counter/ (right?)
In other words, each use of /foreach/ causes
foo() to keep its state information for each
client (right?)

What's more, /foreach/ does not seem to merely /call/
foo(), it does something else, because NOT using
/foreach/ but calling foo() explicitly resets
the counter for every call. Here is an example
for two generators/iterators. I want to pipe the
output of the number-generator into a generator
that packs them up into arrays of three numbers
each.

0 1 2 3 4 5 6 7 8 ... from NumberEnumerabl e

is fed into /ThreeNumbersEnu merable/ and
spits

{0 1 2} {3 4 5} {6 7 8} ...
Here is my naive implementation that does not
work:
namespace TestEnumeration
{
public class NumberEnumerabl e
{

// So far so good, spitting numbers
public IEnumerator<int > GetEnumerator()
{
int counter = 0;
for (; ; )
{
yield return counter++;
}
}
}

// The packer...
public class ThreeNumberEnum erable
{
int[] three = new int[3];
NumberEnumerabl e nen = new NumberEnumerabl e();

public IEnumerator<int[]> GetEnumerator()
{
for(;;)
{
for(int i = 0; i < 3; i++)
{
// Let's call GetEnumerator!
// Let's get numbers!
three[i] = nen.GetEnumerat or().Current;
nen.GetEnumerat or().MoveNext() ;
}

// Return the next 3-pack
yield return three;
}
}
}

class Program
{
static void Main(string[] args)
{
ThreeNumberEnum erable lb = new ThreeNumberEnum erable();

// DISAPPOINTMENT!
// We get packs of {0 0 0} -- the counter
// is reset each time

foreach (int[] i3 in lb)
{
Console.WriteLi ne("{0} {1} {2}", i3[0], i3[1], i3[2]);
}
}
}
}

All this means I can't CALL nen.GetEnumerat or(), I have
to do something else, but what? Am I supposed to do it
this way in first place?

Kind regards
Tin

Jun 29 '06 #4
Tin Gherdanarra <ti*********@gm ail.com> wrote:
A big advantage of generators is that you can recycle
them and use them in the fashion of command-line pipes,
like this:

RandomNumberGen erator | FilterPrimeNumb ersGenerator | MultiplyBy4...

I assumed I can do the same with IEnumerable, but it
seems that it is a lot more tricky than I thought.
You can do this easily. It's the basis of LINQ in C# 3.0. It's trivial:

---8<---
IEnumerable<T> Filter<T>(IEnum erable<T> source, Predicate<T> pred)
{
foreach (T item in source)
if (pred(item))
yield return item;
}
--->8---

The LINQ extension method "Where" (System.Query.S equence.Where() ) looks
like the above.
The key is that I (wrongly) guessed that /yield/ is just a mechanism
for marking the spot where a function returns this time
and will continue next time.
You were right when you guessed. That's exactly what 'yield return' is:
the point where execution will resume when MoveNext() is called on the
enumerator.
/foreach/ simply calls the
function embodying the /yield/. This can't be true, I guess.
Here is an illustration:

int foo()
{
int counter = 0;
yield counter++;
}

Multiple clients can call foo, and the
spot where to continue has to be remembered for each
new caller. This means that the first call to /foo/
somehow instantiates a new environment for that
particular foo(), complete with its own version
of /counter/ (right?)
I think you need to download .NET Reflector and investigate a class
which implements an iterator, and similarly investigate how foreach is
implemented. It would take too long to explain in full here.
In other words, each use of /foreach/ causes
foo() to keep its state information for each
client (right?)
When foreach is first entered, GetEnumerator is called on the enumerable
object and a *new* object implementing IEnumerator[<T>] is returned.
MoveNext() is called once per iteration, and if it ever returns false,
the loop is exited. The value of the Current property is used to
initialize the iteration variable.

The function implementing the iterator is technically never entered.
It's rewritten into a completely different method which lives in a
different class, automatically created by the compiler. Check it out
with .NET Reflector.

One fundamental piece that you seem to be missing is that the local
variables in the iterator definition do *not* lose their values when the
function returns. In particular, for iterators returning IEnumerable
rather than IEnumerator, you should use local variables for iteration
storage, not fields.
I want to pipe the
output of the number-generator into a generator
that packs them up into arrays of three numbers
each.


Here's one solution to that problem:

---8<---
using System;
using System.Collecti ons.Generic;

class App
{
static IEnumerable<int > Count()
{
int i = 0;
for (;;)
yield return i++;
}

static IEnumerable<T[]> SplitIntoGroups <T>(IEnumerable <T> source,
int count)
{
List<T> result = new List<T>();
foreach (T item in source)
{
result.Add(item );
if (result.Count == count)
{
yield return result.ToArray( );
result.Clear();
}
}
}

static IEnumerable<T> TakeN<T>(IEnume rable<T> source, int count)
{
foreach (T item in source)
{
if (count <= 0)
yield break;
--count;
yield return item;
}
}

static void Main()
{
foreach (int[] item in SplitIntoGroups (TakeN(Count(), 100), 3))
Console.WriteLi ne("({0}, {1}, {2})",
item[0], item[1], item[2]);
}
}
--->8---

-- Barry

--
http://barrkel.blogspot.com/
Jun 29 '06 #5
Tin Gherdanarra <ti*********@gm ail.com> wrote:
Here is my naive implementation that does not
work:
I thought I'd go through it and point out each place where there's
evidence of a faulty assumption.
namespace TestEnumeration
{
public class NumberEnumerabl e
{

// So far so good, spitting numbers
public IEnumerator<int > GetEnumerator()
Implementing an iterator which returns IEnumerator is useful when you
want to iterate over an existing collection contained inside the class.
It's handy when you're creating your own collections.

However, when you want to get generator-like chaining behaviour, you
should accept and return IEnumerable rather than IEnumerator.
{
int counter = 0;
for (; ; )
{
yield return counter++;
}
}
}

// The packer...
public class ThreeNumberEnum erable
{
int[] three = new int[3];
NumberEnumerabl e nen = new NumberEnumerabl e();
You don't want to keep these fields, because they'll be shared across
every enumerator, causing separate enumerations to interfere with each
other.
public IEnumerator<int[]> GetEnumerator()
Ditto for the IEnumerator vs IEnumerable as above.
{
for(;;)
{
for(int i = 0; i < 3; i++)
{
// Let's call GetEnumerator!
// Let's get numbers!
three[i] = nen.GetEnumerat or().Current;
Every time you call GetEnumerator, it starts again from the beginning.
Calling GetEnumerator() creates a new object. When you call MoveNext()
on a freshly created enumerator, it enters the start of
NumberEnumerabl e.GetEnumerator () method above. The reason you keep
getting 0 is that you keep creating a new enumerator every time around
the loop.
nen.GetEnumerat or().MoveNext() ;
}

// Return the next 3-pack
yield return three;
}
}
}

class Program
{
static void Main(string[] args)
{
ThreeNumberEnum erable lb = new ThreeNumberEnum erable();

// DISAPPOINTMENT!
// We get packs of {0 0 0} -- the counter
// is reset each time

foreach (int[] i3 in lb)
{
Console.WriteLi ne("{0} {1} {2}", i3[0], i3[1], i3[2]);
}
}
}
}

All this means I can't CALL nen.GetEnumerat or(), I have
to do something else, but what? Am I supposed to do it
this way in first place?


You could have called GetEnumerator() at the start of the function
(getting an IEnumerator), and worked with that IEnumerator throughout
the loop. It isn't as nice to deal with as foreach, though. Usually it's
best to structure your loops so that foreach is applied to the
enumerator if you can possibly help it.

-- Barry

--
http://barrkel.blogspot.com/
Jun 30 '06 #6

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

10
2752
by: jcc | last post by:
Hi guys, I'm a newbie to C#. My Visual Studio 2005 failed to compile the following code with error as 'HelloWorld.A' does not implement interface member 'System.Collections.IEnumerable.GetEnumerator()'. 'HelloWorld.A.GetEnumerator()' is either static, not public, or has the wrong return type. class A : IEnumerable<string>
3
5109
by: KH | last post by:
I can't seem to figure this one out... I've searched MSDN and Goog, and made my best guesses to no avail,, so help would be much appreciated! public ref class T sealed : public System::Collections::IEnumerable , public System::Collections::Generic::IEnumerable<int> { // How to implement GetEnumerator() for both interfaces? // Compiler complains that functions differ only in return type // which I understand, but can't get the right...
1
2981
by: =?Utf-8?B?RnJhbmNvaXNWaWxqb2Vu?= | last post by:
Hi there, Does anybody know how to return the results of an IEnumerable<typeas an array of the same type i.e type in a web service call without first having to collect all elements in the IEnumerable<typeand storing it in memory first? This question is really about optimizing large collections of data returned by web services. If it is possible to loop through the IEnumerable<type> with a "foreach" and somehow start formatting the...
2
4240
by: =?Utf-8?B?a2VubmV0aEBub3NwYW0ubm9zcGFt?= | last post by:
When creating multiple iterators, the original is defined as returning IEnumerator, ie public IEnumerator GetEnumerator() { yield x; ...} whereas the additional ones are defined as returning IEnumerable, ie public IEnumerable AnotherSortOrder() { yield x;....} Any insights out there as to why the additional iteration methods did not just return IEnumerator? (its just a little confusing, hoping for some better insight)
2
4586
by: Morgan Cheng | last post by:
In .Net 2.0, Generics are introduced. I found that IEnumerable<T> inherites from IEnumerable. This makes implementation of IEnumerable<Thas to have two GetEnumerator methods defined( one for IEnumerable<Tand the other for IEnumerable). I doubt why .Net class hierarchy is designed in such a way. IMHO, they should not have inheritance releationship, just like IList<Tand IList. I googled the web and found two related articles....
4
4571
by: Ronald S. Cook | last post by:
Thanks guys for all the guidance on this so far. In the below, for _IEnumerable2, I'm just trying to hard-code one record with "(Please Select)" for a value. I can do that easily in SQL but don't know how here. Any contunued help very much appreciated... Ron Dim _IEnumerable1 As IEnumerable(Of IEnumerable) = _
12
2411
by: gnewsgroup | last post by:
I've read the msdn doc about IEnumerable. It seems to me that IEnumerable objects are essentially wrapped-up arrays. It simply gives us the foreach convenience. Is this correct?
2
2499
by: Tony Johansson | last post by:
Hello! Below I have a working program. I have one generic class called Farm<T> with this header definition public class Farm<T: IEnumerable<Twhere T : Animal Now to my question I changed the inheritance of the IEnumerable from the generic IEnumerable<T> to the generel IEnumerable and the program function just the same so no
1
2018
by: =?Utf-8?B?UElFQkFMRA==?= | last post by:
Something I found surprising this week involves the IEnumerable<Tinterface. I have a class that I wrote a couple of years ago which implements the IEnumerable interface. This week I realized it should implement IEnumerable<T>. But when I changed the return type of the GetEnumerator method the compiler said the class no longer implemented IEnumerable. IEnumerable<Thas IEnumerable as a base interface, so I had assumed that implementing...
0
9407
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
10170
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
10014
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
9841
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
1
7384
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
6656
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
5280
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
5425
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
3
2808
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.