Re: IEnumerable is strange
- From: Barry Kelly <barry.j.kelly@xxxxxxxxx>
- Date: Fri, 30 Jun 2006 00:51:08 +0100
Tin Gherdanarra <tinman31337@xxxxxxxxx> wrote:
A big advantage of generators is that you can recycle
them and use them in the fashion of command-line pipes,
like this:
RandomNumberGenerator | FilterPrimeNumbersGenerator | 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>(IEnumerable<T> source, Predicate<T> pred)
{
foreach (T item in source)
if (pred(item))
yield return item;
}
--->8---
The LINQ extension method "Where" (System.Query.Sequence.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.Collections.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>(IEnumerable<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.WriteLine("({0}, {1}, {2})",
item[0], item[1], item[2]);
}
}
--->8---
-- Barry
--
http://barrkel.blogspot.com/
.
- References:
- IEnumerable is strange
- From: Tin Gherdanarra
- Re: IEnumerable is strange
- From: Barry Kelly
- Re: IEnumerable is strange
- From: Tin Gherdanarra
- IEnumerable is strange
- Prev by Date: error creating timer_elapsed handler.
- Next by Date: How to get SP params?
- Previous by thread: Re: IEnumerable is strange
- Next by thread: Re: IEnumerable is strange
- Index(es):
Relevant Pages
|