Figure 2

Figure 2 Invoking a Delegate Object
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
// At this point, fbChain refers to a delegate that calls 
// FeedbackToMsgBox and this delegate refers to another delegate that 
// calls FeedbackToConsole and this delegate refers to null.

// Now, let's invoke the head delegate which internally invokes
// its previous delegate and so on.
if (fbChain != null) fbChain(null, 0, 10);
// Note: In the line above, fbChain will never be null but checking a 
// delegate reference against null before attempting to invoke it is a
// very good habit to get into.
Figure 3 Combining a Delegate Object to a Chain
Feedback fb = new Feedback(FeedbackToConsole);
Feedback fbChain = (Feedback) Delegate.Combine(fb, fb);
// fbChain refers to a chain of 2 delegate objects.
// One of the objects is identical to the object referred to by fb
// The other object was constructed by Combine. This object's 
// _prev field refers to fb and Combine returns the reference 
// to this new object.

// Do fb and fbChain refer to the same exact object? False
Console.WriteLine((Object) fb == (Object) fbChain);

// Do fb and fbChain refer to the same callback target/method? True
Console.WriteLine(fb.Equals(fbChain));
Figure 4 Calling Delegate.Remote
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
// fbChain refers to a chain of 2 delegates

// Invoke the chain: 2 methods are called
if (fbChain != null) fbChain(null, 0, 10);

fbChain = (Feedback)
   Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
// fbChain refers to a chain of 1 delegate

// Invoke the chain: 1 method is called
if (fbChain != null) fbChain(null, 0, 10);

fbChain = (Feedback)
   Delegate.Remove(fbChain, new Feedback(FeedbackToConsole));
// fbChain refers to a chain of 0 delegates (fbChain is null)

// Invoke the chain: 0 methods are called
if (fbChain != null) fbChain(null, 0, 10);
// Now you see why I've been comparing fbChain to null all this time!
Figure 5 Removing Delegates
Feedback fb = new Feedback(FeedbackToConsole);
Feedback fbChain = (Feedback) Delegate.Combine(fb, fb);
// fbChain refers to a chain of 2 delegates

// Invoke the chain: FeedbackToConsole is called twice
if (fbChain != null) fbChain(...);

// Remove one of the callbacks from the chain
fbChain = (Feedback) Delegate.Remove(fbChain, fb);

// Invoke the chain: FeedbackToConsole is called once
if (fbChain != null) fbChain(...);

// Remove one of the callbacks from the chain
fbChain = (Feedback) Delegate.Remove(fbChain, fb);

// Invoke the chain: 0 methods are called
if (fbChain != null) fbChain(...);
C#'s Support for Delegate Chains
Figure 6 Combining and Removing Delegates in C#
Feedback fb = new Feedback(FeedbackToConsole);
App appobj = new App();
fb += new Feedback(appobj.FeedbackToStream);

// Invoke the chain: FeedbackToStream & FeedbackToConsole are called
if (fb != null) fb(...);

// Remove one of the callbacks from the chain
fb -= new Feedback(FeedbackToConsole);

// Invoke the chain: FeedbackToStream is called
if (fb != null) fb(...);

// Remove the last callback from the chain
fb -= new Feedback(appobj.FeedbackToStream);

// Invoke the chain: 0 methods are called
if (fb != null) fb(...);
Figure 7 GetInvocationList
using System;
using System.Text;

// Define a Light component
class Light {
   // This method returns the light's status
   public String SwitchPosition() { return "The light is off"; }
}

// Define a Fan component
class Fan { 
   // This method returns the fan's status
   public String Speed() { throw new Exception("The fan broke due to 
                                               overheating"); }
}

// Define a Speaker component
class Speaker {
   // This method returns the speaker's status
   public String Volume() { return "The volume is loud"; }
}

class App {

   // Definition of delegate that allows quering a component's status
   delegate String GetStatus();

   static void Main() {
      // Declare an empty delegate chain
      GetStatus getStatus = null;

      // Construct the 3 components and add their status methods 
      // to the delegate chain
      getStatus += new GetStatus(new Light().SwitchPosition);
      getStatus += new GetStatus(new Fan().Speed);
      getStatus += new GetStatus(new Speaker().Volume);

      // Show consolidated status report reflecting 
      // the condition of the 3 components
      Console.WriteLine(GetComponentStatusReport(getStatus));
   }

   // Method that queries several components and returns a status report
   static String GetComponentStatusReport(GetStatus status) {

      // If the chain is empty, there is nothing to do
      if (status == null) return null;

      // Use this to build the status report
      StringBuilder report = new StringBuilder();

      // Get an array where each element is a delegate from the chain
      Delegate[] arrayOfDelegates = status.GetInvocationList();

      // Iterate over each delegate in the array 
      foreach (GetStatus getStatus in arrayOfDelegates) {

         try {
            // Get a component's status string and append it to the report
            report.Append(getStatus() + "\r\n\r\n");
         }
         catch (Exception e) {
            // Generate an error entry in the report for this component
            Object component = getStatus.Target;
            report.Append("Failed to get status from " +
               ((component == null) ? "" : component.GetType() + ".") + 
               getStatus.Method.Name + 
               "\r\n   Error: " + e.Message + "\r\n\r\n");
         }
      }

      // Return the consolidated report to the caller
      return report.ToString();
   }
}