Patterns and Iterators in C#

In this fifth part of a ten-part series on C#, you will learn about the try method pattern, enumeration, and more. It is excerpted from chapter four of C# 3.0 in a Nutshell, Third Edition, A Desktop Quick Reference, written by Joseph Albahari and Ben Albahari (O'Reilly; ISBN: 0596527578). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 2
October 16, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Common Patterns

The try method pattern

When writing a method, you have a choice, when something goes wrong, to return some kind of failure code or throw an exception. In general, you throw an exception when the error is outside the normal workflow—or if you expect that the immediate caller won’t be able to cope with it. Occasionally, though, it can be best to offer both choices to the consumer. An example of this is the int type, which defines two versions of its Parse method:

  public int Parse     (string input);
  public bool TryParse (string input, out int returnValue);

If parsing fails,Parsethrows an exception;TryParse returnsfalse.

You can implement this pattern by having theXXX method call theTryXXX method as follows:

  public return-type XXX (input-type input)
  {
    return-type returnValue;
    if (! TryXXX (input, out returnValue))
      throw new YYYException (...)
    return returnValue;
  }

The atomicity pattern

It can be desirable for an operation to be atomic, where it either successfully completes or fails without affecting state. An object becomes unusable when it enters an indeterminate state that is the result of a half-finished operation. finally blocks facilitate writing atomic operations.

In the following example, we use anAccumulatorclass that has anAddmethod that adds an array of integers to its fieldTotal. TheAddmethod will cause anOverflowException ifTotalexceeds the maximum value for anint. TheAdd method is atomic, either successfully updatingTotalor failing, which leavesTotalwith its former value.

  class Test
 
{
    static void Main()
    {
     
Accumulator a = new Accumulator ();
      try
      {
        
a.Add (4, 5);             // a.Total is now 9
       
a.Add (1, int.MaxValue);  // will cause OverflowException
      }
      catch (OverflowException)
      {
       
Console.WriteLine (a.Total);  // a.Total is still 9
      }
    }
  }

In the implementation ofAccumulator, theAdd method affects theTotalfield as it executes. However, if anything goes wrong during the method (e.g., a numeric overflow, a stack overflow, etc.),Totalis restored to its initial value at the start of the method.

  public class Accumulator
  {
    public int Total;

    public void Add(params int[] ints)
    {
      bool success = false;
      int totalSnapshot = Total;
      try
      {
       
foreach (int i in ints)
       
{
          checked
          {
           
Total += i;
         
}
        }
        success = true;
     
}
      finally
      {
        if (! success)
          Total = totalSnapshot;
      }
    }
  }

Alternatives to exceptions

As with int.TryParse, a function can communicate failure by sending an error code back to the calling function via a return type or parameter. Although this can work with simple and predictable failures, it becomes clumsy when extended to all errors, polluting method signatures and creating unnecessary complexity and clutter. It also cannot generalize to functions that are not methods, such as operators (e.g., the division operator) or properties. An alternative is to place the error in a common place where all functions in the call stack can see it (e.g., a static method that stores the current error per thread). This, though, requires each function to participate in an error-propagation pattern that is cumbersome and, ironically, itself error-prone.

Enumeration and Iterators

Enumeration

An enumerator is a read-only, forward-only cursor over a sequence of values. An enumerator is an object that either:

  • ImplementsIEnumeratororIEnumerator<T>
     
  • Has a method namedMoveNext for iterating the sequence, and a property calledCurrentfor getting the current element in the sequence

Theforeachstatement iterates over an enumerable object. An enumerable object is the logical representation of a sequence. It is not itself a cursor, but an object that produces cursors over itself. An enumerable object either:

  • ImplementsIEnumerableorIEnumerable<T> 
  • Has a method namedGetEnumeratorthat returns an enumerator

IEnumeratorandIEnumerableare defined inSystem.Collections.

IEnumerator<T>andIEnumerable<T>are defined inSystem.Collections.Generic.

The enumeration pattern is as follows:

  class Enumerator   // typically implements IEnumerator or IEnumerator<T>
  {
   
public IteratorVariableType Current { get {...} }
   
public bool MoveNext()         {...}
  }

  class Enumerable   // typically implements IEnumerable or IEnumerable<T>
  {
   
public Enumerator GetEnumerator() {...}
  }

Here is the high-level way of iterating through the characters in the word “beer” using aforeachstatement:

  foreach (char c in "beer")
    Console.WriteLine (c);

Here is the low-level way of iterating through the characters in “beer” without using aforeachstatement:

  var enumerator = "beer".GetEnumerator();

  while (enumerator.MoveNext())
  {
   
var element = enumerator.Current;
   
Console.WriteLine (element);
 
}

Theforeachstatement also acts as ausing statement, implicitly disposing the enumerator object.

Chapter 7 explains the enumeration interfaces in further detail.

Iterators

 

Whereas a foreach statement is a consumer of an enumerator, an iterator is a producer of an enumerator. In this example, we use an iterator to return a sequence of Fibonacci numbers (where each number is the sum of the previous two):

  using System;
  using System.Collections.Generic;

  class Test
  {
    static void Main()
    {
      foreach (int fib in Fibs(6))
        Console.Write (fib + " ");
    }

    static IEnumerable<int>Fibs(int fibCount)
    {
     
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
     
{
       
yield return prevFib;
      
int newFib = prevFib+curFib;
       
prevFib = curFib;
       
curFib = newFib;
     
}
    }
  }

  OUTPUT: 1 1 2 3 5 8

Whereas areturnstatement expresses “Here’s the value you asked me to return from this method,” ayield return statement expresses “Here’s the next element you asked me to yield from this enumerator.” On eachyieldstatement, control is returned to the caller, but the callee’s state is maintained so that the method can continue executing as soon as the caller enumerates the next element. The lifetime of this state is bound to the enumerator, such that the state can be released when the caller has finished enumerating.

Iterator Semantics

An iterator is a method, property, or indexer that contains one or more yield statements. An iterator must return one of the following four interfaces (otherwise, the compiler will generate an error):

  // Enumerable interfaces 
  System.Collections.IEnumerable
  System.Collections.Generic.IEnumerable<T>

  // Enumerator interfaces
  System.Collections.IEnumerator
  System.Collections.Generic.IEnumerator<T>

An iterator has different semantics, depending on whether it returns an enumerable interface or an enumerator interface. We describe this in Chapter 7.

Multiple yield statements are permitted. For example:

  class Test
 
{
   
static void Main()
   
{
     
foreach (string s in Foo())
        Console.WriteLine(s);      // prints "One","Two","Three"
    }

    static IEnumerableFoo()
   
{
      yield return "One";
      yield return "Two";
      yield return "Three";
    }
  }

Theyield breakstatement indicates that the iterator block should exit early, without returning more elements. We can modifyFooas follows to demonstrate:

  static IEnumerable<string>Foo(bool breakEarly)
 
{
    yield return "One";
    yield return "Two";

    if (breakEarly)
      
yield break;

    yield return "Three";
  }

Areturnstatement is illegal in an iterator block. Instead, ayield breakstatement is used to terminate the iteration.

Please check back next week for the continuation of this article.

blog comments powered by Disqus
C# ARTICLES

- Beginning C#
- ASP.NET RedirectPermanent Method using C# an...
- C Programming Language and UNIX Pioneer Pass...
- Using Facebook JavaScript SDK in ASP.NET wit...
- ASP.NET Export to Excel and Word using VB.NE...
- WAV and MP3 Streaming with ASP.Net and C#
- Game Programming using SDL: the File I/O API
- C# and Java Developer Jobs on the Rise
- The Future Evolution of C# and VB.NET
- C# If and Else-if Statements
- How To Use the C# String Replace Method
- 5 Ways to Parse XML in C#
- C# Meets Design Patterns
- Coding a CRC-Generating Algorithm in C
- Cyclic Redundancy Check

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 11 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials