C#'s yield is only half a coroutine. You can't have two iterators
calling each other like you can with full coroutines.
C#'s yield works strictly on a pull-basis. Yield return / yield break is
converted by a mechanical transformation into a state machine
implemented by a class that implements IEnumerable and probably also
In order to get the push behaviour you're talking about, you either need
to pull from the other end, as it were, or create a buffer along with a
processing thread so that whatever is pulling from the IEnumerator (that
they got from an IEnumerable created from the yield etc.) gets values
C#'s iterator feature (aka yield etc.) is quite composable, but the way
to think about the composability is that you make a number of calls to
create a flow graph, and you then create instances of the flow graph.
For example, given an iterator that returns a somewhat infinite stream
int x = 0;
for (;;) yield return x++;
And given a filter creator:
IEnumerable<T> Filter<T>(IEnumerable<T> source, Predicate<T> filter)
foreach (T item in source) if (filter(item)) yield return item;
And given a predicate which checks for even numbers:
bool IsEven(int value)
return (value % 2) == 0;
... one can then compose a graph:
IEnumerable<int> evens = Filter(Ints(), IsEven);
... but the "graph" as it were isn't actually instantiated until you
call GetEnumerator() on it, or do so indirectly via 'foreach'.
I myself have put together a few experimental libraries that combine
threading, background operations, marshalling to the UI etc. to create
dataflow graphs along these patterns. It can work well enough, as long
as you don't try to get too clever.
It's easy in C# to create the equivalent of "dataflow variables" in the
style of Oz etc., but it's also very easy to get a deadlock due to the
way they mix with stateful imperative programming. Also, OS-scheduled
threads are not cheap.