Coroutines in C#

2008-10-31

I've always suffered from getting lost in code that is heavily distributed in object oriented systems. Providing context along with method parameters and calling interfaces from deep within algorithms makes me feel detached from the specific problem I am solving.

Coroutines are nice if your language supports them.

C#'s support is limited in the way that you can use iterators to simulate coroutines. For example, instead of calling an interface, you might build something that returns commands:

enum SyncCommand
{
  CreateNew,
  Delete,
  Copy
};

IEnumerable<SyncCommand> sync()
{
  // ... algorithm interleaved with the following lines:
  yield return SyncCommand.CreateNew;
  yield return SyncCommand.Delete;
  yield return SyncCommand.Copy;
}

Interpreting the result would look like a simple interpreter which moves the control up to the caller:

foreach (var cmd in sync())
{
  switch (cmd)
  {
    case SyncCommand.CreateNew:
    ...
    break;
    case SyncCommand.Delete:
    ...
    break;
    case SyncCommand.Copy:
    ...
    break;
  }
}

Alternatively, to provide arguments to the caller, we may return actions targeted to an interface.

interface ISyncClient
{
  void create(string path, byte[] content);
  void delete(string path);
  void copy(string fromPath, string toPath);
};

The algorithm code would now return lambda expressions instead of enumerations, for example:

IEnumerable<Action<ISyncClient>> sync()
{
  ...
  var pathToCreate = "c:/file.txt";
  var content = new byte[10];
  yield return sc => sc.create(path, content);
  ...
}

And the caller stays in full control by actually running the returned lambdas against a ISyncClient implementor:

class MySyncClient : ISyncClient
{
    ... ISyncClient implementation
}

var sc = new MySyncClient();

foreach (var action in sync())
{
  action(sc);  
}

Returning values to the algorithm is possible by capturing them. Imagine each of the create, delete and copy methods would return a boolean our synchronization algorithm needs to react on:

IEnumerable<Action<ISyncClient>> sync()
{
  var r = false;
  yield return sc => r = sc.copy(...);
  if (!r)
    { /* report some error */ }
}

Using this proposed way to move control to the caller is not always a good thing. There are advantages and disadvantages, but I hope I have shown you another tool for your development toolbox.

A note regarding the generated code: The iterator methods are transformed to state machines by the C# compiler. Each fragment between a yield call gets a different state block. A look under the hood using .NET Reflector is the best way to grasp the magic behind the transformation.

yours armin