You are viewing our Forum Archives. To view or take place in current topics click here.
Some General C# Updated.
Posted:

Some General C# Updated.Posted:

SARG3NT
  • TTG Senior
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Advanced C#


This chapter is excerpted from C# 3.0 in a Nutshell, Third Edition: A Desktop Quick Reference by Joseph Albahari, Ben Albahari, published by O'Reilly Media

In this chapter, we cover advanced C# topics that build on concepts explored in previous chapters. You should read the first four sections sequentially; you can read the remaining sections in any order.


Delegates


[b]A delegate dynamically wires up a method caller to its target method. There are two aspects to a delegate: type and instance. A delegate type defines a protocol to which the caller and target will conform, comprising a list of parameter types and a return type. A delegate instance refers to one (or more) target methods conforming to that protocol.

A delegate instance literally acts as a delegate for the caller: the caller invokes the delegate, and then the delegate calls the target method. This indirection decouples the caller from the target method.

A delegate type declaration is preceded by the keyword delegate, but otherwise it resembles an (abstract) method declaration. For example:
delegate int Transformer (int x);


To create a delegate instance, you can assign a method to a delegate variable:
class Test
{
  static void Main(  )
  {Transformer t = Square;          // create delegate instance
    int result = t(3);               // invoke delegate
    Console.WriteLine (result);
  }
  static int Square (int x) { return x * x; }
}


Invoking a delegate is just like invoking a method (since the delegate's purpose is merely to provide a level of indirection):

t(3);


This statement:

Transformer t = Square;


is shorthand for:

Transformer t = new Transformer(Square);


[b]Tip
A delegate is similar to a callback, a general term that captures constructs such as C function pointers.


Writing Plug-in Methods with Delegates


A delegate variable is assigned a method dynamically. This is useful for writing plug-in methods. In this example, we have a utility method named Transform that applies a transform to each element in an integer array. The Transform method has a delegate parameter, for specifying a plug-in transform.

public delegate int Transformer (int x);

public class Util
{
  public static void Transform (int[] values,Transformer t)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = t(values[i]);
  }
}

class Test
{
  static void Main(  )
  {
    int[] values = new int[] {1, 2, 3};
    Util.Transform(values, Square);      // dynamically hook in Square
    foreach (int i in values)
      Console.Write (i + "  ");           // 1   4   9
  }

  static int Square (int x) { return x * x; }
}



Multicast Delegates


All delegate instances have multicast capability. This means that a delegate instance can reference not just a single target method, but also a list of target methods. The += operator combines delegate instances. For example:

SomeDelegate d = SomeMethod1;
d += SomeMethod2;


Invoking d will now call both SomeMethod1 and SomeMethod2. Delegates are invoked in the order they are added.

The -= method removes the right delegate operand from the left delegate operand. For example:

d -= SomeMethod1;


Invoking d will now cause only SomeMethod2 to be invoked.

Calling += on a delegate variable with a null value works, and it is equivalent to assigning the variable to a new value:

SomeDelegate d = null;
d += SomeMethod1;       // equivalent (when d is null) to d = SomeMethod1;


If a multicast delegate has a nonvoid return type, the caller receives the return value from the last method to be invoked. The preceding methods are still called, but their return values are discarded. In most scenarios in which multicast delegates are used, they have void return types, so this subtlety does not arise.


Tip
All delegate types implicitly inherit System.MulticastDelegate, which inherits from System.Delegate. C# compiles += and -= operations made on a delegate to the static Combine and Remove methods of the System.Delegate class.


Multicast delegate example


Suppose you wrote a routine that took a long time to execute. That routine could regularly report progress to its caller by invoking a delegate. In this example, the HardWork routine has a ProgressReporter delegate parameter, which it invokes to indicate progress:

public delegate void ProgressReporter (int percentComplete);

public class Util
{
  public static void HardWork (ProgressReporter p)
  {
    for (int i = 0; i < 10; i++)
    {
      p (i * 10);                          // Invoke delegate
      System.Threading.Thread.Sleep(100);  // Simulate hard work
    }
  }
}


To monitor progress, the Main method creates a multicast delegate instance p, such that progress is monitored by two independent methods:

class Test
{
  static void Main (  )
  {
    ProgressReporter p = WriteProgressToConsole;
    p += WriteProgressToFile;
    Util.HardWork (p);
  }

  static void WriteProgressToConsole (int percentComplete)
  {
    Console.WriteLine (percentComplete);
  }

  static void WriteProgressToFile (int percentComplete)
  {
    System.IO.File.WriteAllText ("progress.txt", percentComplete.
        ToString(  ));
  }
}



Instance Method Targets


When a delegate instance is assigned to an instance method, the delegate instance must maintain a reference not only to the method, but also to the instance of that method. The System.Delegate class's Target property represents this instance (and will be null for a delegate referencing a static method). For example:

public delegate void ProgressReporter (int percentComplete);

class Test
{
  static void Main() {new Test(  );}
  Test (  )
  {
    ProgressReporter p = InstanceProgress;
    p(99);                                  // 99
    Console.WriteLine (p.Target == this);   // True
    Console.WriteLine (p.Method);           // Void InstanceProgress(Int32)
  }

  void InstanceProgress (int percentComplete)
  {
    Console.WriteLine(percentComplete);
  }
}



Generic Delegate Types


A delegate type may contain generic type parameters. For example:

public delegate T Transformer<T> (T arg);


With this definition, we can write a generalized Transform utility method that works on any type:

public class Util
{
  public static void Transform<T> (T[] values,Transformer<T> t)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = t(values[i]);
  }
}

class Test
{
  static void Main(  )
  {
    int[] values = new int[] {1, 2, 3};
    Util.Transform(values, Square);      // dynamically hook in Square
    foreach (int i in values)
      Console.Write (i + "  ");           // 1   4   9
  }

  static int Square (int x) { return x * x; }
}


Delegates Versus Interfaces

A problem that can be solved with a delegate can also be solved with an interface. For instance, the following explains how to solve our filter problem using an ITransformer interface:

public interface ITransformer
{
  int Transform (int x);
}

public class Util
{
 public static void TransformAll (int[] values, ITransformer t)
 {
   for (int i = 0; i < values.Length; i++)
     values[i] = t.Transform(values[i]);
 }
}

class Test : ITransformer
{
 static void Main(  )
 {
   int[] values = new int[] {1, 2, 3};
   Util.TransformAll(values, new Test(  ));
   foreach (int i in values)
     Console.WriteLine (i);
 }

 public int Transform (int x) { return x * x; }
}


A delegate design may be a better choice than an interface design if one or more of these conditions are true:


  • The interface defines only a single method

  • Multicast capability is needed

  • The listener needs to implement the interface multiple times


In the ITransformer example, we don't need to multicast. However, the interface defines only a single method. Furthermore, our listener may need to implement ITransformer multiple times, to support different transforms, such as square or cube. With interfaces, we're forced into writing a separate type per transform, since Test can only implement ITransformer once. This is quite cumbersome:

class Test
{
 static void Main(  )
 {
   int[] values = new int[] {1, 2, 3};
   Util.TransformAll(values, new Cuber(  ));
   foreach (int i in values)
     Console.WriteLine (i);
 }

 class Squarer : ITransformer
 {
   public int Transform (int x) { return x * x; }
 }
 class Cuber : ITransformer
 {
   public int Transform (int x) {return x * x * x; }
 }
}



Delegate Compatibility


Type compatibility[/b]

Delegate types are all incompatible with each other, even if their signatures are the same:

delegate void D1(  );
delegate void D2(  );
...

D1 d1 = Method1;
D2 d2 = d1;                           // compile-time error


Delegate instances are considered equal if they have the same method targets:

delegate void D(  );
...

D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2);         // true


Parameter compatibility

When you call a method, you can supply arguments that have more specific types than the parameters of that method. This is ordinary polymorphic behavior. For exactly the same reason, a delegate can have more specific parameter types than its method target. This is called contravariance.

Consider the following example:

delegate void SpecificDelegate (SpecificClass s);

class SpecificClass {}

class Test
{
  static void Main(  )
  {
    SpecificDelegate specificDelegate = GeneralHandler;
    specificDelegate (new SpecificClass(  ));
  }

  static void GeneralHandler(object o)
  {
    Console.WriteLine(o.GetType(  )); // SpecificClass
  }
}


A delegate merely calls a method on someone else's behalf. In this case, the SpecificDelegate is invoked with an argument of type SpecificClass. When the argument is then relayed to the target method, the argument gets implicitly upcast to an object.

Tip
The standard event pattern is designed to help you leverage contravariance through its use of the common EventArgs base class. For example, you can have a single method invoked by two different delegates, one passing a MouseEventArgs and the other passing a KeyEventArgs.


Return type compatibility


If you call a method, you may get back a type that is more specific than what you asked for. This is ordinary polymorphic behavior. For exactly the same reason, the return type of a delegate can be less specific than the return type of its target method. This is called covariance. Consider the following example:

delegate Asset DebtCollector(  );

class Asset {}

class House : Asset {}

class Test
{
  static void Main(  )
  {
     DebtCollector d = new DebtCollector (GetHomeSweetHome);
     Asset a = d(  );
     Console.WriteLine(a.GetType(  )); // House
  }
  static House GetHomeSweetHome() {return new House(  ); }
}


A delegate merely calls a method on someone else's behalf. In this case, the DebtCollector expects to get back an Asset-but any Asset will do. Delegate return types are said to be covariant.


Events


When using delegates, two emergent roles commonly appear: broadcaster and subscriber.

The broadcaster is a type that contains a delegate field. The broadcaster decides when to broadcast, by invoking the delegate.

The subscribers are the method target recipients. A subscriber decides when to start and stop listening, by calling += and -= on the broadcaster's delegate. A subscriber does not know about, or interfere with, other subscribers.

Events are a language feature that formalizes this pattern. An event is a wrapper for a delegate that exposes just the subset of delegate features required for the broadcaster/subscriber model. The main purpose of events is to prevent subscribers from interfering with each other.

To declare an event member, you put the event keyword in front of a delegate member. For instance:

public class Broadcaster
{
  publicevent ProgressReporter Progress;
}


Code within the Broadcaster type has full access to Progress and can treat it as a delegate. Code outside of Broadcaster can only perform += and -= operations on Progress.

Consider the following example. The Stock class invokes its PriceChanged event every time the Price of the Stock changes:

public delegate void PriceChangedHandler (decimal oldPrice,
                                          decimal newPrice);

public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) {this.symbol = symbol;}public event PriceChangedHandler PriceChanged;

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;      // exit if nothing has changed
      if (PriceChanged != null)        // if invocation list not empty
        PriceChanged (price, value);   // fire event
      price = value;
    }
  }
}


If we remove the event keyword from our example so that PriceChanged becomes an ordinary delegate field, our example would give the same results. However, Stock would be less robust, in that subscribers could do the following things to interfere with each other:

  • Replace other subscribers by reassigning PriceChanged (instead of using the += operator)

  • Clear all subscribers (by setting PriceChanged to null).

  • Broadcast to other subscribers by invoking the delegate



Standard Event Pattern


The .NET Framework defines a standard pattern for writing events. Its purpose is to provide consistency across both Framework and user code. At the core of the standard event pattern is System.EventArgs: a predefined Framework class with no members (other than the static Empty property). EventArgs is a base class for conveying information for an event. In our Stock example, we would subclass EventArgs to convey the old and new prices when a PriceChanged event is fired:

public class PriceChangedEventArgs : System.EventArgs
{
  public readonly decimal LastPrice;
  public readonly decimal NewPrice;

  public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
  {
    LastPrice = lastPrice;
    NewPrice = newPrice;
  }
}


For reusability, the EventArgs subclass is named according to the information it contains (rather than the event for which it will be used). It typically exposes data as properties or as read-only fields.

With an EventArgs subclass in place, the next step is to choose or define a delegate for the event. There are three rules:


  • It must have a void return type.

  • It must accept two arguments: the first of type object, and the second a subclass of EventArgs. The first argument indicates the event broadcaster, and the second argument contains the extra information to convey.

  • Its name must end in "EventHandler"


The Framework defines a generic delegate called System.EventHandler<> that satisfies these rules:

public delegate void EventHandler<TEventArgs>
  (object source, TEventArgs e) where TEventArgs : EventArgs;


Tip
Before generics existed in the language (prior to C# 2.0), we would have had to instead write a custom delegate as follows:


public delegate void PriceChangedHandler (object sender,
   PriceChangedEventArgs e);

For historical reasons, most events within the Framework use delegates defined in this way.

The next step is to define an event of the chosen delegate type. Here, we use the generic EventHandler delegate:


public class Stock
{
  ...public event EventHandler<PriceChangedEventArgs> PriceChanged;
}


Finally, the pattern requires that you write a protected virtual method that fires the event. The name must match the name of the event, prefixed with the word "On", and then accept a single EventArgs argument:

public class Stock
{
  ...

  public event EventHandler<PriceChangedEventArgs> PriceChanged;protected virtual void OnPriceChanged (PriceChangedEventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }
}


This provides a central point from which subclasses can invoke or override the event.

Here's the complete example:

using System;

public class PriceChangedEventArgs : EventArgs
{
  public readonly decimal LastPrice;
  public readonly decimal NewPrice;

  public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
  {
    LastPrice = lastPrice; NewPrice = newPrice;
  }
}

public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) {this.symbol = symbol;}

  public event EventHandler<PriceChangedEventArgs> PriceChanged;protected virtual void OnPriceChanged (PriceChangedEventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      OnPriceChanged (new PriceChangedEventArgs (price, value));
      price = value;
    }
  }
}

class Test
{
  static void Main(  )
  {
    Stock stock = new Stock ("THPW");
    stock.Price = 27.10M;
    // register with the PriceChanged event
    stock.PriceChanged += stock_PriceChanged;
    stock.Price = 31.59M;
  }

  static void stock_PriceChanged (object sender, PriceChangedEventArgs e)
  {
    if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
      Console.WriteLine ("Alert, 10% stock price increase!");
  }
}


The predefined nongeneric EventHandler delegate can be used when an event doesn't carry extra information. In this example, we rewrite Stock such that the PriceChanged event is fired after the price changes, and no information about the event is necessary, other than it happened. We also make use of the EventArgs.Empty property, in order to avoid unnecessarily instantiating an instance of EventArgs.


public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) {this.symbol = symbol;}

  public eventEventHandler PriceChanged;

protected virtual void OnPriceChanged (EventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      price = value;
      OnPriceChanged (EventArgs.Empty);
    }
  }
}



Event Accessors


An event's accessors are the implementations of its += and -= functions. By default, accessors are implemented implicitly by the compiler. Consider this event declaration:

public event EventHandler PriceChanged;


The compiler converts this to the following:


  • A private delegate field

  • A public pair of event accessor functions, whose implementations forward the += and -= operations to the private delegate field


You can take over this process by defining explicit event accessors. Here's a manual implementation of the PriceChanged event from our previous example:


private EventHandler _PriceChanged;         // declare a private delegate

public event EventHandler PriceChanged
{add
  {
    _PriceChanged += value;
  }
  remove
  {
    _PriceChanged -= value;
  }
}


This example is functionally identical to C#'s default accessor implementation. The add and remove keywords after the event declaration instruct C# not to generate a default field and accessor logic.

With explicit event accessors, you can apply more complex strategies to the storage and access of the underlying delegate. There are three scenarios where this is useful:


  • When the event accessors are merely relays for another class that is broadcasting the event

  • When the class exposes a large number of events, where most of the time very few subscribers exist, such as a Windows control. In such cases, it is better to store the subscriber's delegate instances in a dictionary, since a dictionary will contain less storage overhead than dozens of null delegate field references

  • When explicitly implementing an interface that declares an event


Here is an example that illustrates the last point:


public interface IFoo
{
  event EventHandler Ev;
}

class Foo : IFoo
{
  private EventHandler ev;

  event EventHandler IFoo.Ev
  {
    add    { ev += value; }
    remove { ev -= value; }
  }
}


Tip
The add and remove parts of an event are compiled to add_XXX and remove_XXX methods. The += and -= operations on an event are compiled to calls to the add_XXX and remove_XXX methods.


Event Modifiers


Like methods, events can be virtual, overridden, abstract, and sealed. Events can also be static:


public class Foo
{
  public static event EventHandler<EventArgs> StaticEvent;
  public virtual event EventHandler<EventArgs> VirtualEvent;
}



Lambda Expressions (C# 3.0)


A lambda expression is an unnamed method written in place of a delegate instance. The compiler immediately converts the lambda expression to either:


  • A delegate instance

  • An expression tree, of type Expression<T>, representing the code inside the lambda expression in a traversable object model. This allows the lambda expression to be interpreted later at runtime (see the section "the section called "Building Query Expressions" in Chapter 8, LINQ Queries).


In the following example, square is assigned the lambda expression x = > x * x:


delegate int Transformer (int i);

class Test
{
  static void Main(  )
  {
    Transformer square =x => x * x;
    Console.WriteLine (square(3));    // 9
  }
}


We could rewrite the example by converting the lambda expression into a method, and then call the method through the delegate. In fact, the compiler internally performs that translation for you when you assign a delegate a lambda expression:

delegate int Transformer (int i);

class Test
{
  static void Main(  )
  {
    Transformer square = Square;
    Console.WriteLine (square(3));    // 9
  }
  static int Square (int x) {return x * x;}
}


A lambda expression has the following form:

(parameters) => expression-or-statement-block



For convenience, you can omit the parentheses if and only if there is exactly one parameter of an inferable type.

In our example, there is a single parameter, x, and the expression is x * x:

x => x * x;


Each parameter of the lambda expression corresponds to a delegate parameter, and the type of the expression (which may be void) corresponds to the return type of the delegate.

In our example, x corresponds to parameter i, and the expression x * x corresponds to the return type int, therefore being compatible with the Transformer delegate:

delegate int Transformer (int i);


A lambda expression's code can be a statement block instead of an expression. We can rewrite our example as follows:

x => {return x * x;};



Explicitly Specifying Lambda Parameter Types


The compiler can usually infer the type of lambda parameters contextually. When this is not the case, you must specify the type of each parameter explicitly. Consider the following delegate type:

delegate int Transformer (int i);


The compiler uses type inference to infer that x is an int, by examining Transfomer's parameter type:

Transformer d = x => x * x;


We could explicitly specify x's type as follows:

Transformer d = (int x) => x * x;



Generic Lambda Expressions and the Func Delegates


With generic delegates, it becomes possible to write a small set of delegate types that are so general they can work for methods of any return type and any (reasonable) number of arguments. These delegates are the Func and Action delegates, defined in the System namespace:

delegate TResult Func <T>                   (  );
delegate TResult Func <T1,TResult>          (T1 arg1);
delegate TResult Func <T1,T2,TResult>       (T1 arg1, T2 arg2);
delegate TResult Func <T1,T2,T3,TResult>    (T1 arg1, T2 arg2, T3 arg3);
delegate TResult Func <T1,T2,T3,T4,TResult> (T1 arg1, T2 arg2, T3 arg3,
                                                   T4 arg4);

delegate void Action                              (  );
delegate void Action  <T>                   (T1 arg1);
delegate void Action  <T1,T2>               (T1 arg1, T2 arg2);
delegate void Action  <T1,T2,T3>            (T1 arg1, T2 arg2, T3 arg3);
delegate void Action  <T1,T2,T3,T4>         (T1 arg1, T2 arg2, T3 arg3,
                                                   T4 arg4);



These delegates are extremely general. The Transformer delegate in our previous example can be replaced with a Func delegate that takes a single int argument and returns an int value:

class Test
{
  static void Main(  )
  {
    Func<int,int> square = x => x * x;
    Console.WriteLine (square(3));     // 9
  }
}



Outer Variables


A lambda expression can reference the local variables and parameters of the method in which it's defined. For example:

delegate int NumericSequence (  );

class Test
{
  static void Main(  )
  {
    int seed = 0;
    NumericSequence natural = (  ) =>seed++;
    Console.WriteLine (natural(  ));           // 0
    Console.WriteLine (natural(  ));           // 1
  }
}



Local variables and parameters referenced by a lambda expression are called outer variables. In our example, seed is an outer variable referenced by the lambda expression ( ) => seed++. Outer variables are captured, meaning their lifetime is extended to that of the lambda expression. Let's refactor the example to make the effect of capturing more striking:


delegate int NumericSequence (  );

class Test
{
  static NumericSequence Natural (  )
  {
    int seed = 0;         // executes once  (per call to Natural(  ))
    return (  ) => seed++;  // executes twice (per call to delegate instance
                          //                 returned by Natural(  ))
  }

  static void Main(  )
  {
    NumericSequence natural = Natural (  );
    Console.WriteLine (natural(  ));           // 0
    Console.WriteLine (natural(  ));           // 1
  }
}


The local variable seed would ordinarily just pop off the stack when the Natural method exits. However, seed is captured by the lambda expression of the delegate instance returned by Natural. This means the lifetime of seed is extended to the lifetime of that delegate instance. Subsequent invocations of that same delegate instance will reuse the same seed variable.

[b]Tip
Capturing is internally implemented by "lifting" the captured variables into fields of a private class. When the method is called, the class is instantiated and lifetime-bound to the delegate instance.

A local variable instantiated within a lambda expression is unique per invocation of the delegate instance. If we refactor our previous example to instantiate seed within the lambda expression, we get a different (in this case, undesirable) result:

delegate int NumericSequence (  );

class Test
{
  static NumericSequence Natural (  )
  {
    return (  ) => {int seed = 0; return seed++; };
  }

  static void Main(  )
  {
    NumericSequence natural = Natural (  );
    Console.WriteLine (natural(  ));                 // 0
    Console.WriteLine (natural(  ));                 // 0
  }
}





Anonymous Methods



Anonymous methods are a C# 2.0 feature that has been subsumed by C# 3.0 lambda expressions. An anonymous method is like a lambda expression, but it lacks the following features:


  • Implicitly typed parameters

  • Expression syntax (an anonymous method must always be a statement block)

  • The ability to compile to an expression tree, by assigning to Expression<T>



To write an anonymous method, you include the delegate keyword followed by a parameter declaration and then a method body. For example:


delegate int Transformer (int i);

class Test
{
  static void Main(  )
  {
    Transformer square =delegate (int x) {return x * x;};
    Console.WriteLine (square(3));    // 9
  }
}


The following line:

Transformer square =delegate (int x)    {return x * x;};


is semantically equivalent to the following lambda expression:

Transformer square =(int x) => {return x * x;};


Or simply:

Transformer square =x  => x * x;


Anonymous methods capture outer variables in the same way lambda expressions do.



Try Statements and Exceptions


A try statement specifies a code block subject to error-handling or cleanup code. The try block must be followed by a catch block, a finally block, or both. The catch block executes when an error occurs in the try block. The finally block executes after execution leaves the try block (or if present, the catch block), to perform cleanup code, whether or not an error occurred.

A catch block has access to an Exception object that contains information about the error. You use a catch block to either compensate for the error or rethrow the exception. You rethrow an exception if you merely want to log the problem, or if you want to rethrow a new, higher-level exception type.

A finally block adds determinism to your program, by always executing no matter what. It's useful for cleanup tasks such as closing network connections.

A try statement looks like this:


try
{
  ... // exception may get thrown within execution of this block
}
catch (ExceptionA ex)
{
  ... // handle exception of type ExceptionA
}
catch (ExceptionB ex)
{
  ... // handle exception of type ExceptionB
}
finally
{
  ... // cleanup code
}


Consider the following program:


class Test
{
  static int Calc (int x) {return 10 / x;}

  static void Main(  )
  {
    int y = Calc (0);
    Console.WriteLine (y);
  }
}


Because x is zero, the runtime throws a DivideByZeroException, and our program terminates. We can prevent this by catching the exception as follows:


class Test
{
  static int Calc (int x) {return 10 / x;}

  static void Main(  )
  {try
    {
      int y = Calc (0);
      Console.WriteLine (y);
    }
    catch (DivideByZeroException ex)
    {
      Console.WriteLine("x cannot be zero");
    }
    Console.WriteLine ("program completed");
  }
}

OUTPUT:
x cannot be zero
program completed


When an exception is thrown, the CLR performs a test:

Is execution currently within a try statement that can catch the exception?

  • If so, execution is passed to the compatible catch block. If the catch block successfully finishes executing, execution moves to the next statement after the try statement (if present, executing the finally block first).

  • If not, execution jumps back to the caller of the function, and the test is repeated (after executing any finally blocks that wrap the statement).


If no function takes responsibility for the exception, an error dialog is displayed to the user, and the program terminates.


The catch Clause


A catch clause specifies what type of exception to catch. This must either be System.Exception or a subclass of System.Exception.

Catching System.Exception catches all possible errors. This is useful when:


  • Your program can potentially recover regardless of the specific exception type

  • You plan to rethrow the exception (perhaps after logging it)

  • Your error handler is the last resort, prior to termination of the program


More typically, though, you catch specific exception types, in order to avoid having to deal with circumstances for which your handler wasn't designed (e.g., an OutOfMemoryException).

You can handle multiple exception types with multiple catch clauses:


class Test
{
  static void Main (string[] args)
  {
    try
    {
      byte b = byte.Parse (args[0]);
      Console.WriteLine (b);
    }
    catch (IndexOutOfRangeException ex)
    {
      Console.WriteLine ("Please provide at least one argument");
    }
    catch (FormatException ex)
    {
      Console.WriteLine ("That's not a number!");
    }
    catch (OverflowException ex)
    {
      Console.WriteLine ("You've given me more than a byte!");
    }
  }
}


Only one catch clause executes for a given exception. If you want to include a safety net to catch more general exceptions (such as System.Exception), you must put the more specific handlers first.

An exception can be caught without specifying a variable, if you don't need to access its properties:


catch (StackOverflowException)   // no variable
{
  ...
}


Furthermore, you can omit both the variable and the type (meaning that all exceptions will be caught):


catch { ... }


Tip
In languages other than C#, it is possible (though not recommended) to throw an object that does not derive from Exception. The CLR automatically wraps that object in a RuntimeWrappedException class (which does derive from Exception).


The finally Block


A finally block always executes-whether or not an exception is thrown and whether or not the try block runs to completion. finally blocks are typically used for cleanup code.

A finally block executes either:


  • After a catch block finishes

  • After control leaves the try block because of a jump statement (e.g., return or goto)

  • After the try block ends


A finally block helps add determinism to a program. In the following example, the file that we open always gets closed, regardless of whether:


  • The try block finishes normally.

  • Execution returns early because the file is empty (EndOfStream)

  • An IOException is thrown while reading the file.



using System;
using System.IO;

class Test
{
  static void Main (  )
  {
    StreamReader reader = null;
    try
    {
      reader = File.OpenText ("file.txt");
      if (reader.EndOfStream)return;
      Console.WriteLine (reader.ReadToEnd (  ));
    }
    finally
    {
      if (reader != null) reader.Dispose (  );
    }
  }
}


In this example, we closed the file by calling Dispose on the StreamReader. Calling Dispose on an object, within a finally block, is a standard convention throughout the .NET Framework and is supported explicitly in C# through the using statement.



The using statement


Many classes encapsulate unmanaged resources, such as file handles, graphics handles, or database connections. These classes implement System.IDisposable, which defines a single parameterless method named Dispose to clean up these resources. The using statement provides an elegant syntax for calling Dispose on an IDisposable object within a finally block.

The following:


using (StreamReader reader = File.OpenText ("file.txt"))
{
  ...
}


is precisely equivalent to:


StreamReader reader = File.OpenText ("file.txt");
try
{
  ...
}
finally
{
  if (reader != null)
   ((IDisposable)reader).Dispose(  );
}


We cover the disposal pattern in more detail in Chapter 12, Disposal and Garbage Collection.



Throwing Exceptions


Exceptions can be thrown either by the runtime or in user code. In this example, Display throws a System.ArgumentNullException:


class Test
{
  static void Display (string name)
  {
    if (name == null)throw new ArgumentNullException ("name");

    Console.WriteLine (name);
  }

  static void Main(  )
  {
    try { Display (null); }
    catch (ArgumentNullException ex)
    {
      Console.WriteLine ("Caught the exception");
    }
  }
}




Rethrowing an exception


You can capture and rethrow an exception as follows:


try {  ...  }
catch (Exception ex)
{
  // Log error
  ...throw;          // Rethrow same exception
}


Rethrowing in this manner lets you log an error without swallowing it. It also lets you back out of handling an exception should circumstances turn out to be outside what you expected:


using System.Net;       // (See Chapter 14)
...

string s;
using (WebClient wc = new WebClient(  ))
  try { s = wc.DownloadString ("http://albahari.com/");  }
  catch (WebException ex)
  {
    if (ex.Status == WebExceptionStatus.NameResolutionFailure)
      Console.WriteLine ("Bad domain name");
    elsethrow;     // Can't handle other sorts of WebException, so rethrow
  }


The other common scenario is to rethrow a more specific exception type. For example:


try
{
  ... // parse a date of birth from XML element data
}
catch (FormatException ex)
{
  throw new XmlException ("Invalid date of birth", ex);
}


Rethrowing an exception does not affect the StackTrace property of the exception (see the next section). When rethrowing a different exception, you can set the InnerException property with the original exception if doing so could aid debugging. Nearly all types of exceptions provide a constructor for this purpose.




Key Properties of System.Exception


The most important properties of System.Exception are the following:

StackTrace
A string representing all the methods that are called from the origin of the exception to the catch block.

Message
A string with a description of the error

InnerException
The inner exception (if any) that caused the outer exception. This, itself, may have another InnerException.

Tip
All exceptions in C# are runtime exceptions-there is no equivalent to Java's compile-time checked exceptions.



Common Exception Types


The following exception types are used widely throughout the CLR and .NET Framework. You can throw these yourself or use them as base classes for deriving custom exception types.

System.ArgumentException
Thrown when a function is called with a bogus argument. This generally indicates a program bug

System.ArgumentNullException
Subclass of ArgumentException that's thrown when a function argument is (unexpectedly) null.

System.ArgumentOutOfRangeException
Subclass of ArgumentException that's thrown when a (usually numeric) argument is too big or too small. For example, this is thrown when passing a negative number into a function that accepts only positive values.

System.InvalidOperationException
Thrown when the state of an object is unsuitable for a method to successfully execute, regardless of any particular argument values. Examples include reading an unopened file or getting the next element from an enumerator where the underlying list has been modified partway through the iteration

System.NotSupportedException
Thrown to indicate that a particular functionality is not supported. A good example is calling the Add method on a collection for which IsReadOnly returns true.

System.NotImplementedException
Thrown to indicate that a function has not yet been implemented

System.ObjectDisposedException
Thrown when the object upon which the function is called has been disposed



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, Parse throws an exception; TryParse returns false.

You can implement this pattern by having the XXX method call the TryXXX method as follows:


publicreturn-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 an Accumulator class that has an Add method that adds an array of integers to its field Total. The Add method will cause an OverflowException if Total exceeds the maximum value for an int. The Add method is atomic, either successfully updating Total or failing, which leaves Total with 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 of Accumulator, the Add method affects the Total field as it executes. However, if anything goes wrong during the method (e.g., a numeric overflow, a stack overflow, etc.), Total is 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.


[ Register or Signin to view external links. ]

I am new to C# so don't hate.

Please leave feedback!







Last edited by SARG3NT ; edited 7 times in total
#2. Posted:
-Lee
  • Prospect
Status: Offline
Joined: Jul 21, 201013Year Member
Posts: 650
Reputation Power: 35
Status: Offline
Joined: Jul 21, 201013Year Member
Posts: 650
Reputation Power: 35
wow this is really helpful, deserves a sticky! you should use spoilers though


Last edited by -Lee ; edited 1 time in total
#3. Posted:
Messlah
  • TTG Addict
Status: Offline
Joined: Jan 15, 201014Year Member
Posts: 2,631
Reputation Power: 152
Status: Offline
Joined: Jan 15, 201014Year Member
Posts: 2,631
Reputation Power: 152
I dont really know anything about this but i can see that this had to of taken awhile to make and id rather not see it get buried
#4. Posted:
SARG3NT
  • TTG Senior
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Thanks, Going to put the spoilers in now. No one seems to care though :/
#5. Posted:
JaykPEI
  • TTG Senior
Status: Offline
Joined: Jul 20, 201112Year Member
Posts: 1,174
Reputation Power: 57
Status: Offline
Joined: Jul 20, 201112Year Member
Posts: 1,174
Reputation Power: 57
I don tknow much about this stuff. But i read this over and it looks like a well done post. If I ever need some info on this. Ill come to you
#6. Posted:
SARG3NT
  • TTG Senior
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Thanks, It took me a long time.

Hope some people enjoy it and give me feedback.
#7. Posted:
M0D1F13D
  • TTG Senior
Status: Offline
Joined: Sep 06, 201013Year Member
Posts: 1,069
Reputation Power: 47
Status: Offline
Joined: Sep 06, 201013Year Member
Posts: 1,069
Reputation Power: 47
Wait so...does a delegate just act as a man in the middle when calling a method? It seems simpler to just call the method to me...
#8. Posted:
SARG3NT
  • TTG Senior
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Status: Offline
Joined: Feb 25, 201113Year Member
Posts: 1,496
Reputation Power: 69
Yeah, You are right. Thanks for the feedback though.
#9. Posted:
Shife
  • TTG Senior
Status: Offline
Joined: Jun 08, 201013Year Member
Posts: 1,001
Reputation Power: 46
Status: Offline
Joined: Jun 08, 201013Year Member
Posts: 1,001
Reputation Power: 46
DANG how long did this take? Seems like you know your C#
#10. Posted:
Hiimmanly
  • Prospect
Status: Offline
Joined: May 08, 201112Year Member
Posts: 699
Reputation Power: 28
Status: Offline
Joined: May 08, 201112Year Member
Posts: 699
Reputation Power: 28
Bookmarked. I'll eventually read this.
Jump to:
You are viewing our Forum Archives. To view or take place in current topics click here.