Patterns and Iterators in C#
(Page 1 of 4 )
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.
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.
Next: Enumeration and Iterators >>
More C# Articles
More By O'Reilly Media
|
This article 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). Check it out today at your favorite bookstore. Buy this book now.
|
|