Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Class Construction Techniques

0.00/5 (No votes)
14 Apr 2009 1  
An article that describes interfaces, indexers, and delegates.

Interfaces, Indexers, and Delegates

Nearly everything in .NET is an object. The .NET Framework has thousands of classes, and each class has different methods and properties. Keeping track of all of these classes and members would be impossible if the .NET Framework were not implemented consistently. For example, every class derives from System.Object and therefore has a ToString() method that performs exactly the same task - converting an instance of a class to a string. Similarly, many classes support the same operators, such as for comparing two instances of a class for equality.

This consistency is possible because of inheritance and interfaces. Use inheritance to create new classes from existing ones. For example, the Bitmap class inherits from the Image class and extends it by adding functionality. Therefore you can use an instance of the Bitmap class in the same ways that you would an instance of the Image class.

Interfaces, also known as contracts, define a common set (semantically-related) of members that all classes that implement the interface must provide. For example, the IComparable interface defines the CompareTo method, which enables two instances of a class to be compared for equality. All classes that implement the IComparable interface can be compared for equality. IDisposable is an interface that provides a single method, Dispose, to enable assemblies that create an instance of a class to free up any resources the instance has consumed. Below is an example of a class declaration. After declaring the class, you add the interface:

class SomeClass 
{
}

//Add the interface declaration:

class SomeClass : IDisposable
{
}

At this point, it is important to introduce the concept of indexers. The C# language supports the capability of building custom classes that may be indexed just like an array of intrinsic types. In short, indexers allow you to access items in an array like fashion. More specifically, properties can either take parameters or not take parameters. The get accessor methods for the properties accept no parameters. There are parameterful properties whose get accessor methods accept one or more parameters and whose set accessor methods accept two or more parameters. In C#, parameterful properties (called indexers) are exposed using array-like syntax. You can think of an indexer as a means to overload the [ ] operator. Here is an example of a BitArray class that allows array-like syntax to index into the set of bits maintained by an instance of the class:

using System;

public sealed class BitArray {
    private Int32 m_numBits;

    // Constructor that allocates the byte array and sets all bits to 0
    public BitArray(Int32 numBits) {
        // Validate arguments first.
        if (numBits <= 0)
            throw new ArgumentOutOfRangeException("numBits must be > 0");
    
        // Save the number of bits.
        m_numBits = numBits;
    
        // Allocate the bytes for the bit array.
        m_byteArray = new Byte[(m_numBits + 7) / 8];
    }

    // This is the indexer.
    public Boolean this[Int32 bitPos] {
    
        // This is the index property’s get accessor method.
        get {
            // Validate arguments first
            if ((bitPos < 0) || (bitPos >= m_numBits))
                throw new ArgumentOutOfRangeException("bitPos", 
                    "bitPos must be between 0 and " + m_numBits);
    
            // Return the state of the indexed bit.
            return((m_byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0);
        }

        // This is the index property’s set accessor method.
        set {
            if ((bitPos < 0) || (bitPos >= m_numBits))
                throw new ArgumentOutOfRangeException("bitPos", 
                   "bitPos must be between 0 and " + m_numBits);
        
            if (value) {
                // Turn the indexed bit on.
                m_byteArray[bitPos / 8] = 
                  (Byte)(m_byteArray[bitPos / 8] | (1 << (bitPos % 8)));
            }
            else {
                // Turn the indexed bit off.
                m_byteArray[bitPos / 8] = 
                  (Byte)(m_byteArray[bitPos / 8] & ~(1 << (bitPos % 8)));
            }
        }
    }
}

public sealed class Program {

    public static void Main() {
        // Allocate a BitArray that can hold 14 bits.
        BitArray ba = new BitArray(14);

        // Turn all the even-numbered bits on by calling the set accessor.
        for (Int32 x = 0; x < 14; x++) {
            ba[x] = (x % 2 == 0);
        }

        // Show the state of all the bits by calling the get accessor.
        for (Int32 x = 0; x < 14; x++) {
            Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off"));
        }
    }
}

Here is the output:

Bit 0 is On
Bit 1 is Off
Bit 2 is On
Bit 3 is Off
Bit 4 is On
Bit 5 is Off
Bit 6 is On
Bit 7 is Off
Bit 8 is On
Bit 9 is Off
Bit 10 is On
Bit 11 is Off
Bit 12 is On
Bit 13 is Off

Before explaining the code example above, let us look at a more basic example to expand on the indexer. We will build a set of classes and compile them into DLLs using the ‘/target:library’ option of the csc.exe compiler. Here is an example called Car.cs. It will depend on a class file called Radio.cs. We will first declare the Radio class:

using System;

public class Radio
{
    public Radio()
    {}

    public void TurnOn(bool on)
    {
        if(on)
            Console.WriteLine("How do you hear it...");
        else
            Console.WriteLine("Silence...");
    }
}

This code compiles on the command line with C:\...\v2.0.50727>csc.exe /t:library Radio.cs.

Now, let us write a class called Car:

using System;

public class Car
{
    // Internal state data.
    private int currSpeed;
    private int maxSpeed;
    private string petName;

    // Out of gas?
    bool dead;

    // A car has a radio
    private Radio theMusicBox;

    public Car()
    {
        maxSpeed = 100;
        dead = false;
        // Outer class creates the inner class(es)
        // upon start-up.
        // NOTE:  If we did not, theMusicBox would
        // begin as a null reference.
        theMusicBox = new Radio();
    }

    public Car(string name, int max, int curr)
    {
        currSpeed = curr;
        maxSpeed = max;
        petName = name;
        dead = false;
        theMusicBox = new Radio();
    }

    public void CrankTunes(bool state)
    {
        // Tell the radio play (or not).
        // Delegate request to inner object.
        theMusicBox.TurnOn(state);
    }

    public void SpeedUp(int delta)
    {
        // If the car is dead, just say so...
        if(dead)
        {
            Console.WriteLine("{0} is out of order....", petName);
        }
        else    // Not dead, speed up.
        {
            currSpeed += delta;
            if(currSpeed >= maxSpeed)
            {
        Console.WriteLine("{0} has overheated...", petName);
                dead = true;
            }
            else
                Console.WriteLine("\tCurrSpeed = {0}", currSpeed);
        }
    }

    // Properties.
    public string PetName
    {
        get { return petName; }
        set { petName = value;}
    }
    public int CurrSpeed
    {
        get { return currSpeed; }
        set { currSpeed = value;}
    }
    public int MaxSpeed
    {
        get { return maxSpeed; }
        set { maxSpeed = value;}
    }
}

Note that an indexer’s set accessor method also contains a hidden parameter, called value in C#. This parameter indicates the new value desired for the indexed element. This file is compiled in the same way, the source code compiles into a DLL. Because it contains a Radio object, the compiler must reference that DLL:

C:\..\v2.0.50727>csc /target:library /r:Radio.dll Car.cs

Now here is another class called Cars (plural). This example depends on the previous class files, and will exemplify the indexer.

using System;
using System.Collections;

public class Cars : IEnumerator, IEnumerable
{
    // This class maintains an array of cars.
    private Car[] carArray;
    
    // Current position in array.
    int pos = -1;

    public Cars()
    {
        carArray = new Car[10];
    }

    // The indexer.
    public Car this[int pos]
    {
        get
        {
            if(pos < 0 || pos > 10)
                throw new IndexOutOfRangeException("Wait a minute! Index out of range");
            else
                return (carArray[pos]);
        }
        set
        {
            carArray[pos] = value;
        }
    }
    
    // Implementation of IEnumerator.
    public bool MoveNext()
    {
        if(pos < carArray.Length)
        {
            pos++;
            return true;
        }
        else
            return false;
    }
    public void Reset()
    {
        pos = 0;
    }
    public object Current
    {
        get { return carArray[pos]; }
    }
    
    // This must be present in order to let the foreach 
    // expression to iterate over our array.
    // IEnumerable implemtation.
    public IEnumerator GetEnumerator()
    {
        return (IEnumerator)this;
    }
}

C# requires the keyword this (this[...]) as the syntax for expressing and indexer in some cases. This class file will only compile by using the references to the previously built DLLs:

C:\..\v2.0.50727>csc /t:library /r:Radio.dll /r:Car.dll Cars.cs

Now, we can build an application that references those DLLs (to make use of the methods) to use an indexer and iterate over the collection of objects to access the items in an array-like fashion:

using System;
using System.Collections;

public class CarApp
{
    public static void Main()
    {
        Console.WriteLine("Basic array iteration...");
        int[] myInts = {10, 9, 100, 432, 9874};

        // Use the [] operator to access each element.
        for(int j = 0; j < myInts.Length; j++)
            Console.WriteLine("Index {0} = {1}", j,  myInts[j]);

        Cars carLot = new Cars();
        
        // Add to car array.
        carLot[0] = new Car("Lemon", 200, 0);
        carLot[1] = new Car("BadOnGas", 90, 0);
        carLot[2] = new Car("BMW", 30, 0);

        // Now get and display each.
        Console.WriteLine("\nUsing indexer...");
        for(int i = 0; i < 3; i++)
        {
        Console.WriteLine("Car number {0}:", i);
        Console.WriteLine("Name: {0}", carLot[i].PetName);
        Console.WriteLine("Max speed: {0}\n", carLot[i].MaxSpeed);
        }

        try
        {    // Iterate using IEnumerable.
            Console.WriteLine("Using IEnumerable...");
            foreach (Car c in carLot)
            {
                      Console.WriteLine("Name: {0}", c.PetName);
            Console.WriteLine("Max speed: {0}\n", c.MaxSpeed);
            }
        }
        catch{}            
    }
}

Notice that the for loop construct uses the [] (bracket) operator to access each element. We compile this file on the command line with three references to classes that we have built:

C:\..\v2.0.50727>csc.exe /r:Radio.dll /r:Car.dll /r:Cars.dll CarApp.cs 

The output:

Basic array iteration...
Index 0 = 10
Index 1 = 9
Index 2 = 100
Index 3 = 432
Index 4 = 9874

Using indexer...
Car number 0:
Name: Lemon
Max speed: 200

Car number 1:
Name: BadOnGas
Max speed: 90

Car number 2:
Name: BMW
Max speed: 30

Using IEnumerable...
Name: Lemon
Max speed: 200

Name: BadOnGas
Max speed: 90

Name: BMW
Max speed: 30

The best way to learn the concepts of delegates is by example, so I’ll write a very basic example. We will define a delegate by using the delegate keyword:

delegate void PrintMessageDelegate(string name);

What we did was just define a signature of a method, nothing more. Now, let's create a method that matches that signature.

static void PrintMessage(string name)  
{
    Console.WriteLine("Hello {0}!", name);
    // add a standard routine: betcha never seen that one
}

Now we want to instantiate the delegate:

static void Main(string[]  args)
{
    // so we want to create an instance of our delegate to target our PrintMessage method
    // which will be invoked whenver we invoke our delegate
    // we'll call our instance pm
    PrintMessageDelegate pm = new PrintMessageDelegate(PrintMessage);

    // the instance of our delegate wil target our method PrintMessage by creating an object 
    //whenever we invoke pm, it will point to our PrintMessage method
    pm("Skippy");
}

Now, here is the entire file:

using System;
using System.Collections.Generic;
using System.Text;

class Program {
    delegate void PrintMessageDelegate(string name);
    static void PrintMessage (string name)
    {
        Console.WriteLine("Hello {0}!", name);
    }
    static void Main(string[]  args)
    {
        // we'll call our instance pm
        PrintMessageDelegate pm = new PrintMessageDelegate(PrintMessage);
        // the instance of our delegate wil target our method PrintMessage
        // by creating an object whenever we invoke pm, it will point
        // to our PrintMessage method
        pm("Skippy");
    }
}

Output: Hello Skippy!

An important note: An aspect of this very basic example demonstrates how the CLR requires that every object must be created using the new operator. Here is what the new operator does:

  1. It calculates the number of bytes required by all instance fields defined by the type and all of its base types up to and including System.Object. Every object on the heap requires some additional members - called the type object pointer and the sync block index - used by the CLR to manage the object. The bytes for these additional members are added to the size of the object.
  2. It allocates memory for the object by allocating the number of bytes required for the specified type from the managed heap; all of these bytes are then set to zero.
  3. It initiates the object's type object pointer and sync block index member.
  4. The type's instance constructor is called, passing it any arguments specified in the call to new. After new has performed these operations, it returns a reference (or pointer) to the newly created object. In the preceding example, this reference was saved in the variable pm (which functioned as an instance of our delegate that when invoked, will point to our method PrintMessage). Below is an example of an anonymous delegate. Notice that in the example above, the method that our delegate has targeted has a name. Rather than pointing at it (PrintMessage), let's assign it an anonymous delegate (anonymous delegates were introduced in .NET 2.0).
PrintMessageDelegate pm = delegate(string name);

Use the delegate keyword as if you were writing a method, but you neither specify a name nor a return value. It will match that up depending on what is in the code body of the method definition. That parameter list is the same as the signature of the delegate that your assigning it to.

using System;
using System.Collections.Generic;
using System.Text;

class TestDelegate
{
    delegate void PrintMessageDelegate(string name);
   
    static PrintMessageDelegate GetMessagePrinter(string message)
    {
        return delegate(string name)
        {
            Console.WriteLine(message, name);
        };
    }

    static void Main(string[] args)
    {
        PrintMessageDelegate pm = GetMessagePrinter("Hello {0}!");
        pm("World");
        pm("Joe");
        pm("David");

        pm = GetMessagePrinter("Goodbye {0}.");
        pm("World");

    }
}

A Closer Look at Delegates

Under the hood, the keyword delegate represents a class deriving from the System.MultiCastDelegate namespace. Thus, if you write:

public delegate void PlayAcidRock( object Jimmy Hendricks, int volume);

the C# compiler produces a new class that looks something like the following:

public class  PlayAcidRock :  System.MultiCastDelegate
{
    PlayAcidRock(object target, int ptr);
    // The Synchronous Invoke() method
    public void virtual Invoke(object Jimmy Hendricks, int volume);
    // you also receive an asynchronous version of the same callback
    public virtual IAsyncResult BeginInvoke(object Jimmy Hendricks, 
                   int volume, AsyncCallback cb, object o);
    public virtual void EndInvoke(IAsynchResult result);
}

The Asynchronous Program Model uses “Begin and End” methods that pre-pend to a method name in order for code to complete during an I/O operation without having to wait for the I/O operation to complete, as you have to with synchronous code. On that note, we will update the previous code used to describe interfaces and inheritance for class construction. We will write an application that references the radio.dll, and we will rearrange the Car.cs file, and the Garage.cs file to then compile into DLLs that will be referenced by our application. Here is the Car.cs file:

using System;

public class Car : Object
{    
    // This delegate encapsulates a function pointer
    // to some method taking a Car and returning void.
    // This is represented as Car$CarDelegate (e.g. is nested).
    public delegate void CarDelegate(Car c);
    
    // The nested car type.
    public class Radio
    {
        public Radio(){}
        public void TurnOn(bool on)
        {
            if(on)
                Console.WriteLine("Jamming...");
            else
                Console.WriteLine("Quiet time...");
        }
    }

    // Internal state data.
    private int currSpeed;
    private int maxSpeed;
    private string petName;
    
    // Outta gas?
    private bool dead;

    // NEW!  Are we in need of a wash?
    private bool isDirty;

    // NEW!  Are we in need of a wash?
    private bool shouldRotate;    
    
    // A car has-a radio.
    private Radio theMusicBox;
    
    public bool Dirty
    {
        get{ return isDirty; }
        set{ isDirty = value; }
    }
    
    public bool Rotate
    {
        get{ return shouldRotate; }
        set{ shouldRotate = value; }
    }
    
    public Car()
    {
        maxSpeed = 100;
        dead = false;
        // Outer class creates the inner class(es)
        // upon start-up.
        // NOTE:  If we did not, theMusicBox would
        // begin as a null reference.
        theMusicBox = new Radio();
    }

    public Car(string name, int max, int curr, bool dirty, bool rotate)
    {
        currSpeed = curr;
        maxSpeed = max;
        petName = name;
        dead = false;
        isDirty = dirty;
        shouldRotate = rotate;
        theMusicBox = new Radio();
    }

    public void CrankTunes(bool state)
    {
        // Tell the radio play (or not).
        // Delegate request to inner object.
        theMusicBox.TurnOn(state);
    }

    public void SpeedUp(int delta)
    {
        // If the car is dead, just say so...
        if(dead)
            Console.WriteLine("Car is already dead...");
        else    // Not dead, speed up.
        {
            currSpeed += delta;
            if(currSpeed >= maxSpeed)
                dead = true;
            else
                Console.WriteLine("\tCurrSpeed = " + currSpeed);
        }
    }
}

To compile on the command line: C:.Net> csc.exe /target:library Car.cs.

Here is the Garage.cs file:

using System;
using System.Collections;

// This delegate encapsulates a function pointer
// to some method taking a Car and returning void.
// public delegate void CarDelegate(Car c);

public class Garage
{
    // We have some cars.
    ArrayList theCars = new ArrayList();

    public Garage()
    {
        theCars.Add(new Car("Viper", 100, 0, true, false));
        theCars.Add(new Car("Fred", 100, 0, false, false));
        theCars.Add(new Car("BillyBob", 100, 0, false, true));
        theCars.Add(new Car("Bart", 100, 0, true, true));
        theCars.Add(new Car("Stan", 100, 0, false, true));
    }

    // This method takes a CarDelegate as a parameter.
    // Therefore!  'proc' is nothing more than a function pointer...
    public void ProcessCars(Car.CarDelegate proc)
    {
        // Where are we passing the call?
        foreach(Delegate d in proc.GetInvocationList())
        {
            Console.WriteLine("***** Calling: {0} *****", 
                              d.Method.ToString());
        }

        // Am I calling an object's method or a static method?
        if(proc.Target != null)
            Console.WriteLine("\n-->Target: " + proc.Target.ToString());
        else
            Console.WriteLine("\n-->Target is a static method");

        // Now call method for each car.
        foreach(Car c in theCars)
            proc(c);    

        Console.WriteLine();
    }
}

We compile this file in the same way, as a DLL. Now, here is our application that will reference Car.dll and Garage.dll:

using System;

// A helper class
public class ServiceDept
{
    // A target for the delegate.
    public void WashCar(Car c)
    {
        if(c.Dirty)
            Console.WriteLine("Cleaning a car");
        else
            Console.WriteLine("This car is already clean...");
    }
    
    // Another target for the delgate.
    public void RotateTires(Car c)
    {
        if(c.Rotate)
            Console.WriteLine("Tires have been rotated");
        else
            Console.WriteLine("Don't need to be rotated...");

    }
}

public class CarApp
{
    // A target for the delegate.
    /*
    public static void WashCar(Car c)
    {
        if(c.Dirty)
            Console.WriteLine("Cleaning a car");
        else
            Console.WriteLine("This car is already clean...");
    }
    
    // Another target for the delgate.
    public static void RotateTires(Car c)
    {
        if(c.Rotate)
            Console.WriteLine("Tires have been rotated");
        else
            Console.WriteLine("Don't need to be rotated...");

    }
    */

    public static int Main(string[] args)
    {
        // Make the garage.
        Garage g = new Garage();

        // Make the service department.
        ServiceDept sd = new ServiceDept();
        
        // Wash all dirty cars.
        //g.ProcessCars(new Car.CarDelegate(sd.WashCar));

        // Rotate the tires.
        //g.ProcessCars(new Car.CarDelegate(sd.RotateTires));

        // Create two new delegates. 
        Car.CarDelegate wash = new Car.CarDelegate(sd.WashCar);
        Car.CarDelegate rotate = new Car.CarDelegate(sd.RotateTires);

        // Store the new delegate for later use.
        MulticastDelegate d = wash + rotate;

        // Send the new delegate into the ProcessCars() method.
        g.ProcessCars((Car.CarDelegate)d);
    
        // Remove the rotate pointer.
        Delegate washOnly = MulticastDelegate.Remove(d, rotate);
        g.ProcessCars((Car.CarDelegate)washOnly);
        return 0;
    }
}

To compile: csc.exe /r:Radio.dll /r:Car.dll /r:Garage.dll Application.cs.

Here is the output:

dele.JPG

If you examine the Main method, you’ll see that it begins by creating an instance of the Garage type. This class has been configured to delegate all work to other named static functions. So, when we write the following:

// wash all those dirty cars 
g.ProcessCars(new Car.CarDelegate)WashCar));

what you are saying is “Add a pointer to the WashCar function to the CarDelegate type, and pass this delegate to Gargage.ProcessCars()”. So, on the surface, delegates, once one spends time working with them, seem easy to use: you use the C# delegate keyword, you construct instances by using the new operator, and you invoke the callback by using the familiar method-call syntax. But, they can get complicated, which is why a lot of C# instructional papers place them in the “Advanced C# Features” section. But, some of these complexities can be clarified. Chaining is a set or collection of delegate objects, and it provides the ability to invoke, or call, all of the methods represented by the delegates in the set. To demonstrate how important chaining is, consider this referenced code that gives a conceptual understanding about delegates:

using System;
using System.Windows.Forms;
using System.IO;


// Declare a delegate type; instances refer to a method that
// takes an Int32 parameter and returns void.
internal delegate void Feedback(Int32 value);


public sealed class Program {
   public static void Main() {
      StaticDelegateDemo();
      InstanceDelegateDemo();
      ChainDelegateDemo1(new Program());
      ChainDelegateDemo2(new Program());
   }

   private static void StaticDelegateDemo() {
      Console.WriteLine("----- Static Delegate Demo -----");
      Counter(1, 3, null);
      Counter(1, 3, new Feedback(Program.FeedbackToConsole));
      Counter(1, 3, new Feedback(FeedbackToMsgBox)); // "Program." is optional
      Console.WriteLine();
   }

   private static void InstanceDelegateDemo() {
      Console.WriteLine("----- Instance Delegate Demo -----");
      Program p = new Program();
      Counter(1, 3, new Feedback(p.FeedbackToFile));

      Console.WriteLine();
   }

   private static void ChainDelegateDemo1(Program p) {
      Console.WriteLine("----- Chain Delegate Demo 1 -----");
      Feedback fb1 = new Feedback(FeedbackToConsole);
      Feedback fb2 = new Feedback(FeedbackToMsgBox);
      Feedback fb3 = new Feedback(p.FeedbackToFile);

      Feedback fbChain = null;
      fbChain = (Feedback) Delegate.Combine(fbChain, fb1);
      fbChain = (Feedback) Delegate.Combine(fbChain, fb2);
      fbChain = (Feedback) Delegate.Combine(fbChain, fb3);
      Counter(1, 2, fbChain);

      Console.WriteLine();
      fbChain = (Feedback) Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
      Counter(1, 2, fbChain);
   }

   private static void ChainDelegateDemo2(Program p) {
      Console.WriteLine("----- Chain Delegate Demo 2 -----");
      Feedback fb1 = new Feedback(FeedbackToConsole);
      Feedback fb2 = new Feedback(FeedbackToMsgBox);
      Feedback fb3 = new Feedback(p.FeedbackToFile);

      Feedback fbChain = null;
      fbChain += fb1;
      fbChain += fb2;
      fbChain += fb3;
      Counter(1, 2, fbChain);

      Console.WriteLine();
      fbChain -= new Feedback(FeedbackToMsgBox);
      Counter(1, 2, fbChain);
   }
   
   private static void Counter(Int32 from, Int32 to, Feedback fb) {
      for (Int32 val = from; val <= to; val++) {
         // If any callbacks are specified, call them
         if (fb != null) 
            fb(val);
      }
   }

   private static void FeedbackToConsole(Int32 value) {
      Console.WriteLine("Item=" + value);
   }

   private static void FeedbackToMsgBox(Int32 value) {
      MessageBox.Show("Item=" + value);
   }

   private void FeedbackToFile(Int32 value) {
      StreamWriter sw = new StreamWriter("Status", true);
      sw.WriteLine("Item=" + value);
      sw.Close();
   }
}

To understand chaining, consider the ChainDelegateDemo1method that appears in this code from Jeffrey Richter’s book: The CLR via C#, 2nd Edition:

private static void ChainDelegateDemo1(Program p) {
  Console.WriteLine("----- Chain Delegate Demo 1 -----");
  Feedback fb1 = new Feedback(FeedbackToConsole);
  Feedback fb2 = new Feedback(FeedbackToMsgBox);
  Feedback fb3 = new Feedback(p.FeedbackToFile);

  Feedback fbChain = null;
  fbChain = (Feedback) Delegate.Combine(fbChain, fb1);
  fbChain = (Feedback) Delegate.Combine(fbChain, fb2);
  fbChain = (Feedback) Delegate.Combine(fbChain, fb3);
  Counter(1, 2, fbChain);

  Console.WriteLine();
  fbChain = (Feedback) Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
  Counter(1, 2, fbChain);
}

In this method, after the Console.WriteLine method, he constructs three delegate objects, and have the variables fb1, fb2, and fb3 refer to each object:

                                _target null
fb1    ---------à _method ptr   FeedbackToConsole
                               -invocation list   null


                             _target null
fb2    ---------à _method ptr   FeedbackToMsgBox
                             -invocation list   null

                             _target  .----------------------àProgram Object
fb3  ---------à _method ptr   FeedbackToFile
                             -invocation list   null

The reference variable to a Feedback delegate object, fbChain, is intended to refer to a chain or set of delegate objects that wrap methods that can be called back. Herein, we find an underlying principle: we learn how to build a chain of objects and how to invoke all of those objects in that chain. All items in the chain are invoked because the delegate type’s Invoke method includes code to iterate through all items in the array, invoking each item. Just like collections - classes used for grouping and managing related objects that allow you to iterate over those objects - are a strong tool. Computers are good at dealing with large amounts of data, so it is important to store that data in an orderly way. But, what good is having the data stored if it cannot be accessed? The generality lies in the class construction techniques of accessing class items by inheritance and interfaces.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here