Recipe6.9.Dealing with Finally Blocks and Iterators


Recipe 6.9. Dealing with Finally Blocks and Iterators

Problem

You have added a try/finally block to your iterator and you notice that the finally block is not being executed when you think it should.

Solution

Wrap a try block around the iteration code in the GetEnumerator iterator with a finally block following this try block:

 public class StringSet {     private List<string> _items = new List<string>();          public void AddString(string str)     {         _items.Add(str);     }     public IEnumerator GetEnumerator()     {          try         {             for (int index = 0; index < _items.Count; index++)             {                  yield return (_items[index]);              }         }         finally         {             Console.WriteLine("In iterator finally block");         }     } } 

The foreach code that calls this iterator looks like this:

 // Create a new StringSet object. StringSet strSet = new StringSet(); // Store string data in the strSet object… // Use the GetEnumerator iterator. foreach (string s in strSet) {     Console.WriteLine(s); } 

When this code is run, the following output is displayed:

 String data1 String data2 … String dataN In iterator finally block 

Move the try/finally block around the yield return statement within the iterator. The new iterator code will look like this:

 public IEnumerator GetEnumerator() {     for (int index = 0; index < _items.Count; index++)     {          try         {             yield return (_items[index]);          }         finally         {             Console.WriteLine("In iterator finally block");         }     } } 

When this code is run, the following output is displayed:

 String data1 In foreach finally block String data2 In foreach finally block … String dataN In foreach finally block In iterator finally block 

Discussion

You may have thought that the output would display the "In iterator finally block" string after displaying each item in the strSet object. However, this is not the way that finally blocks are handled in iterators. If you had a normal function that was structured in exactly the same way (but did something other than a yield return inside the loop), you wouldn't expect the finally block to run once per iteration. You'd expect it to run once. Iterators go out of their way to preserve these semantics. All finally blocks inside the iterator member body are called only after the iterations are complete, code execution leaves the foreach loop (such as when a break, return, or tHRow statement is encountered), or when a yield break statement is executed, effectively terminating the iterator.

To see how iterators deal with catch and finally blocks (note that there can be no catch blocks inside of an iterator member body), consider the following code:

 public static void TestFinallyAndIterators()  {      // Create a StringSet object and fill it with data.     StringSet strSet = new StringSet();      strSet.AddString("item1");      strSet.AddString("item2");     // Display all data in StringSet object.     try     {         foreach (string s in strSet)         {             try             {                 Console.WriteLine(s);             }             catch (Exception)             {                 Console.WriteLine("In foreach catch block");             }              finally              {                 Console.WriteLine("In foreach finally block");              }         }     }     catch (Exception)     {         Console.WriteLine("In outer catch block");     }     finally     {         Console.WriteLine("In outer finally block");     } } 

Assuming that your original StringSet.GetEnumerator method is used (i.e., the one that contained the try/finally block), you will see the following behaviors.

If no exception occurs, you see this:

 item1 In foreach finally block item2 In foreach finally block In iterator finally block In outer finally block 

We see that the finally block that is within the foreach loop is executed on each iteration. However, the finally block within the iterator is executed only after all iterations are finished. Also notice that the iterator's finally block is executed before the finally block that wraps the foreach loop.

If an exception occurs in the iterator itself, during processing of the second element, the following is displayed:

 item1 In foreach finally block  (Exception occurs here…) In iterator finally block In outer catch block In outer finally block 

Notice that immediately after the exception is thrown, the finally block within the iterator is executed. This can be useful if you need to clean up only after an exception occurs. If no exception happens, then the finally block is not executed. After the iterator's finally block executes, the exception is caught by the catch block outside the foreach loop. At this point, the exception could be handled or rethrown. Once this catch block is finished processing, the outer finally block is executed. Notice that the catch block within the foreach loop was never given the opportunity to handle the exception. This is due to the fact that the corresponding try block does not contain a call to the iterator.

If an exception occurs in the foreach loop, during processing of the second element, the following is displayed:

 item1 In foreach finally block  (Exception occurs here…) In foreach catch block In foreach finally block In iterator finally block In outer finally block 

Notice in this situation that the catch and finally blocks within the foreach loop are executed first, then the iterator's finally block. Lastly, the outer finally block is executed.

Understanding the way catch and finally blocks operate inside iterators will allow you to add catch and finally blocks in the correct location. If you need a finally block to execute once, immediately after the iterations are finished, add this finally block to the iterator method. If, however, you want the finally block to execute on each iteration, you need to place the finally block within the foreach loop body.

If you need to catch iterator exceptions immediately after they occur, you should consider wrapping the foreach loop in a TRy/catch block. Any TRy/catch block within the foreach loop body will miss exceptions thrown from the iterator.

See Also

See the "Iterators," "yield," "IEnumerator Interface," and "IEnumerable Interface" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net