FREE BOOK

Chapter 5: Advanced C# Class Construction Techniques

Posted by Apress Free Book | C# Language January 13, 2009
This chapter rounds out your introduction to the core aspects of the C# language by examining a number of advanced (but extremely useful) syntactic constructs. To begin, you learn how to construct and use an indexer method.

Listening to Incoming Events

Assume you have now created an instance of the Car class and wish to listen to the events  it is capable of sending. The goal is to create a method that represents the "event sink" (i.e., the method called by the delegate). To do so, you need to call the correct add_XXX() method to ensure that your method is added to the list of function pointers maintained by your delegate. However, you do not call add_XXX() and remove_XXX() directly, but rather use the overloaded += and -= operators. Basically, when you wish to listen to an event, follow the pattern shown here:

// I'm listening…
// ObjectVariable.EventName += new ObjectVariable.DelegateName(functionToCall);
//
Car.Exploded += new Car.EngineHandler(OnBlowUp OnBlowUp);

When you wish to detach from a source of events, use the -= operator:

// Shut up already!
// ObjectVariable.EventName -= new ObjectVariable.DelegateName(functionToCall);
//
Car.Exploded -= new Car.EngineHandler(OnBlowUp OnBlowUp);

Here is a complete example (output is shown in Figure 5-9):

// Make a car and listen to the events.
public class CarApp
{
    public static int Main(string[] args)
    {
        Car c1 = new Car("SlugBug", 100, 10);
        // Hook into events.
        Car.Exploded += new Car.EngineHandler(OnBlowUp);
        Car.AboutToBlow += new Car.EngineHandler(OnAboutToBlow);
        // Speed up (this will generate the events.)
        for (int i = 0; i < 10; i++) c1.SpeedUp(20);
        // Detach fro from events.
        Car.Exploded -= new Car.EngineHandler(OnBlowUp);
        Car.Exploded -= new Car.EngineHandler(OnAboutToBlow);
        // No response!
        for (int i = 0; i < 10; i++) c1.SpeedUp(20);
        return 0;
    }
    // OnBlo OnBlowUp event sink.
    public static void OnBlowUp(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
    // OnAboutToBlow event sink.
    public static void OnAboutToBlow(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
}

Figure 5-9. Handling your Car's event set

If you wish to have multiple event sinks called by a given event, simply repeat the process:

// Multiple event sinks.
public class CarApp
{
    public static int Main(string[] args)
{
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Hook into events.
Car.Exploded += new Car.EngineHandler(OnBlowUp);
Car.Exploded += new Car.EngineHandler(OnBlowUp2 OnBlowUp2); );
Car.AboutToBlow += new Car.EngineHandler(OnAboutToBlow);
// Speed up (this will generate the events.)
for(int i = 0; i < 10; i++)
c1.SpeedUp(20);
// Detach from events.
Car.Exploded -= new Car.EngineHandler(OnBlowUp);
Car.Exploded -= new Car.EngineHandler(OnBlowUp2);
Car.Exploded -= new Car.EngineHandler(OnAboutToBlow);
}
    // OnBlowUp event sink A.
    public static void OnBlowUp(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
    // OnBlowUp event sink B.
    public static void OnBlowUp2(string s)
    {
        Console.WriteLine("-->AGAIN I say: {0}", s);
    }
    // OnAboutToBlow event sink.
    public static void OnAboutToBlow(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
}

Now, when the Exploded event is sent, the associated delegate calls OnBlowUp() as well as OnBlowUp2(), as shown in Figure 5-10.



Figure 5-10. Working with multiple event handlers

Objects as Event Sinks

At this point, you have the background to build objects that can participate in a two-way conversation. However, understand that you are free to build a helper object to respond to an object's event set, much in the same way that you created a helper class to be called by all delegates. For example, let's move your event sink methods out of the CarApp class and into a new class named CarEventSink:

// Car event sink
public class CarEventSink
{
    // OnBlowUp event handler.
    public void OnBlowUp(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
    // OnBlowUp event handler version 2.
    public void OnBlowUp2(string s)
    {
        Console.WriteLine("-->AGAIN I say: {0}", s);
    }
    // OnAboutToBlow handler.
    public void OnAboutToBlow(string s)
    {
        Console.WriteLine("Message from car: {0}", s);
    }
}

The CarApp class is then a bit more self-contained, as the event sink methods have been pulled out of the CarApp definition and into their own custom type. Here is the update:

// Note the creation and use of the CarEventSink.
public class CarApp
{
    public static int Main(string[] args)
    {
        Car c1 = new Car("SlugBug", 100, 10);
        // Make the sink object.
        CarEventSink sink = new CarEventSink();
        // Hook into events using sink object.
        Car.Exploded += new Car.EngineHandler(sink.OnBlowUp);
        Car.Exploded += new Car.EngineHandler(sink.OnBlowUp2);
        Car.AboutToBlow += new Car.EngineHandler(sink.OnAboutToBlow);
        for (int i = 0; i < 10; i++)
            c1.SpeedUp(20);
        // Detach from events using sink object.
        Car.Exploded -= new Car.EngineHandler(sink.OnBlowUp);
        Car.Exploded -= new Car.EngineHandler(sink.OnBlowUp2);
        Car.Exploded -= new Car.EngineHandler(sink.OnAboutToBlow);
        return 0;
    }
}

The output is (of course) identical.

SOURCE CODE The CarEvents project is located under the Chapter 5 subdirectory.

Designing an Event Interface

COM programmers may be familiar with the notion of defining and implementing "callback interfaces." This technique allows a COM client to receive events from a
coclass using a custom COM interface, and is often used to bypass the overhead imposed by the official COM connection point architecture. For an illustration of using the interface as a callback, let's examine how callback interfaces can be created using C# (and .NET in general). Consider this last topic a bonus section, which proves the point that there is always more than one way to solve a problem.

First, let's keep the same assumption that the Car type wishes to inform the outside world when it is about to blow (current speed is 10 miles below the maximum speed) and has exploded. However, this time you will not be using the "delegate" or "event" keywords, but rather the following custom interface:

// The engine event interface.
public interface IEngineEve IEngineEvents nts
{
void AboutToBlow(string msg);
void Exploded(string msg);
}

This interface will be implemented by a sink object, on which the Car will make calls.

Here is a sample implementation:

// Car event sink.
public class CarEventSink : IEngineEvents
{
    public void AboutToBlow(string msg)
    {
        Console.WriteLine(msg);
    }
    public void Exploded(string msg)
    {
        Console.WriteLine(msg);
    }
}

Now that you have an object that implements the event interface, your next task is to pass a reference to this sink into the Car. The Car holds onto the reference, and makes calls back on the sink when appropriate. In order to allow the Car to obtain a reference to the sink, you can assume some method has been added to the default public interface.

In keeping with the COM paradigm, let's call this method Advise(). When the object user wishes to detach from the event source, he may call another method (Unadvise() in COM-speak). In order to allow the object user to register multiple event sinks, let's assume that the Car maintains an ArrayList to represent each outstanding connection (analogous to the array of IUnknown* interfaces used with classic COM connection points). Here is the story so far:

// This Car does not make any use of C# delegates or events.
public class Car
{
    // The set of connected sinks.
    ArrayList itfConnections = new ArrayList();
    // Attach or disconnect from the source of events.
    public void Advise(IEngineEvents itfClientImpl)
    {
        itfConnections.Add(itfClientImpl);
    }
    public void Unadvise(IEngineEvents itfClientImpl)
    {
        itfConnections.Remove(itfClientImpl);
    }
}

Now, Car.SpeedUp() can be retrofitted to iterate over the list of connections and fire the correct notification when appropriate (i.e., call the correct method on the sink):

// Interface based event protocol!
//
class Car
{
    public void SpeedUp(int delta)
    {
        // If the car is dead, send exploded event to each sink.
        if (dead)
        {
            foreach (IEngineEvents e in itfConnections)
                e.Exploded("Sorry, this car is dead...");
        }
        else
        {
            currSpeed += delta;
            // Dude, you're almost dead! Proceed with caution!
            if (10 = maxSpeed - currSpeed)
            {
                foreach (IEngineEvents e in itfConnections)
                    e.AboutToBlow("Careful buddy! Gonna blow!");
            }
            // Still OK!
            if (currSpeed >= maxSpeed)
                dead = true;
            else
                Console.WriteLine("\tCurrSpeed = {0}", currSpeed);
        }
    }
}

The following is some client-side code, now making use of a callback interface to listen to the Car events:

// Make a car and listen to the events.
public class CarApp
{
    public static int Main(string[] args)
    {
        Car c1 = new Car("SlugBug", 100, 10);
        // Make sink object.
        CarEventSink sink = new CarEventSink();
        // Pass the Car a reference to the sink.
        // (The lab solution registers multiple sinks…).
        c1.Advise(sink);
        // Speed up (this will generate the events.)
        for (int i = 0; i < 10; i++)
            c1.SpeedUp(20);
        // Detach from events.
        c1.Unadvise(sink);
        return 0;
    }
}

The output should look very familiar (see Figure 5-11).



Figure 5-11. Interfaces as an event protocol

SOURCE CODE
The EventInterface project is located under the Chapter 5 subdirectory.

Total Pages : 11 7891011

comments