Dotneteers.net
All for .net, .net for all!

Using nested iterators

The yield return construct has been introduces by C# 2.0 in order to provide an easy way to create iterators returning IEnumerable<T> generic type instances. Many books and articles treat the construct, so I do not want to go into deep details about how it works. I’d rather like to share a new experience of my own with you, the usefulness of nested iterators. It’s not about that I’ve been discovered the warm water again, the interesting thing is that I even did not recognize the opportunities before.

The base issue arrived when I worked on the C# parser used in the LINQ over C# project. When analyzing the C# source code text there are many operations to repeat on all declared type.

“It must be easy” could you say, let’s use the foreach construct to traverse through all the types. However, it is a bit more than just so simple:

—  Types con be declared in one or more source files, the same source file can declare zero, one or more types.

—  Types can be enclosed in namespaces or can be global.

—  Namespaces can be nested to each other.

—  Types can declare nested types.

The following figure illustrates the relation among the aforementioned entities:

My first approach to carry out operations (let us assume it is done by the Operation method) was the following:

// --- Carry out the operation on all types

public void OperationOnAllTypes()

{

  foreach (SourceFile file in Files)

  {

    // --- Iterate through the types of the global namespace

    foreach (TypeDeclaration type in file.TypeDeclarations)

      OperationOnType(type);   

 

    // --- Iterate through the namespaces in the file

    foreach(NamespaceFragment ns in file.NestedNamespaces)

      OperationOnNamespace(ns);

  }

}

 

// --- Carry out the operation on each type in the specified namespace

private void OperationOnNamespace(NamespaceFragment ns)

{

  // --- Iterate through the types in this namespace

  foreach (TypeDeclaration type in ns.TypeDeclarations)

    OperationOnType(type);

 

  // --- Iterate through the nested namespaces

  foreach (NamespaceFragment nested in ns.NestedNamespaces)

    OperationOnNamespace(nested);

}

 

private void OperationOnType(TypeDeclaration type)

{

  // --- Let’s execute the operation on the type

  type.Operation();

 

  // --- Iterate through all the nested types

  foreach (TypeDeclaration nestedType in type.NestedTypes)

    OperationOnType(nestedType);

}

This code extract is not very long; however, it is not clean enough. If the Operation method would contain arguments those should be passed to the OperationOnAllTypes, OperationOnNamespace and OperationOnType methods. I probably should not tell that in the C# parser I had dozens of operations with arguments.

I started to write down this construct for the third time when I got disturbed and had an uncomfortable feeling. It was not about just typing but rather about the unclearness of the pattern. “Give me those iterators!”--I thought. I refactored the pattern above and here is the result:

public IEnumerable<TypeDeclaration> TypesInSource

{

  get

  {

    foreach (SourceFile file in Files)

      foreach (TypeDeclaration type in GetTypesInDeclarationScope(file))

        yield return type;

  }

}

 

public IEnumerable<TypeDeclaration> GetTypesInDeclarationScope(

  ITypeDeclarationScope scope)

{

  // --- Types within the current scope (file or namespace)

  foreach (TypeDeclaration type in scope.TypeDeclarations)

    foreach (TypeDeclaration toReturn in GetTypesInType(type))

      yield return toReturn;

 

  // --- Types in the nested namespaces

  foreach (NamespaceFragment nestedNs in scope.NestedNamespaces)

    foreach (TypeDeclaration toReturn in GetTypesInDeclarationScope(nestedNs))

      yield return toReturn;

}

 

public IEnumerable<TypeDeclaration> GetTypesInType(TypeDeclaration type)

{

  yield return type;

 

  // --- Iterate through the nested types

  foreach (TypeDeclaration nestedType in type.NestedTypes)

    foreach (TypeDeclaration toReturn in GetTypesInType(nestedType))

      yield return toReturn;

}

This source code is not essentially shorter than the original one, it has I huge result: the traversal through the types has been completely separated from the operation carried out on them. Using this pattern it is quite easy to execute the operation on each type:

foreach (TypeDeclaration type in TypesInSource)

{

  Operation();

}

Should the Operation have arguments, we would have to write them only once and not carry on through the iteration cycles. Using the nested iterators our code will be clear and if we have many operations to be done with the iteration items the code will be essentially shorter.


Posted Aug 19 2008, 08:00 AM by inovak
Filed under:

Comments

Visual Studio Hacks wrote Visual Studio Links #67
on Thu, Aug 21 2008 16:32

My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. The Web Developer Tools Team announced the release of the Dynamic Data Wizard Preview 0806 for VS 2008 SP1 . US ISV Developer Evangelism

Add a Comment

(required)  
(optional)
(required)  
Remember Me?