CodeGuru
Earthweb Search
Forums Wireless Jars Gamelan Developer.com
CodeGuru Navigation
RSS Feeds

RSSAll

RSSVC++/C++

RSS.NET/C#

RSSVB

See more EarthWeb Network feeds

follow us on Twitter

Member Sign In
User ID:
Password:
Remember Me:
Forgot Password?
Not a member?
Click here for more information and to register.

Become a Marketplace Partner

jobs.internet.com

internet.commerce
Partners & Affiliates
















Home >> .NET / C# >> .NET >> General >> Events and Delegates


.NET Event Handling using the Template Method Design Pattern
Rating: none

Kevin McFarlane (view profile)
March 18, 2002

Environment: .NET, C#, Visual Basic .NET, Windows 2000, Windows XP, Windows NT4 SP6a

Microsoft .NET event handling, in common with typical object-oriented frameworks, is implemented using the well-known Observer design pattern. (See the book, Design Patterns, by Gamma et al., Addison-Wesley, 1995, pp 325-330). This article describes how to enhance .NET event handling with the Template Method design pattern. The discussion and code snippets are in C# but the summary sample is implemented in both C# and Visual Basic .NET.


(continued)



The article elaborates on ideas discussed by Tomas Restrepo in the March 2002 issue of Visual Systems Journal, which builds on the recommended practice for event handling described by Microsoft in the MSDN Library .NET topic, Design Guidelines for Class Library Developers. (See the sub-topic "Event Usage Guidelines.") 

The simplest strategy for event handling is just to raise an event and not care about who consumes it, or whether different clients need to relate to it in different ways. 

Example - Simple Event Handling

Consider a class, Supplier, that raises an event whenever its name field is set and a class, Client, that handles it.

public class Supplier
{
    public Supplier() {}

    public event EventHandler NameChanged;

    public string Name
    {
        get { return name; }
        set { name = value; OnNameChanged(); }
    }

    private void OnNameChanged()
    {
        // If there are registered clients raise event
        if (NameChanged != null)
            NameChanged(this, new EventArgs());
    }

    private string name;
}

public class Client
{
    public Client()
    {
        // Register for supplier event
        supplier = new Supplier();
        supplier.NameChanged +=
              new EventHandler(this.supplier_NameChanged);
    }

    public void TestEvent()
    {
        // Set the name - which generates an event
        supplier.Name = "Kevin McFarlane";
    }

    private void supplier_NameChanged(object sender, EventArgs e)
    {
        // Handle supplier event
    }

    private Supplier supplier;
}

Clients of an event can be both external and internal.

An "external" client is one that consumes an event but is not related to the class that raises the event. In other words, it is not part of the event class's inheritance tree. The class, Client, above is an external client. 

An "internal" client can be the event-raising class itself, if it's handling its own events, or a subclass of the event-raising class. In such cases, the simple strategy outlined above is inadequate. Clients cannot easily change what happens when the event is raised or what the default behaviour is when handling the event.

To tackle this problem, in the .NET Design Guidelines for Class Library Developers, Microsoft recommends using a protected virtual method for raising each event. This provides a way for subclasses to handle the event using an override. So, in our example, OnNameChanged() should look like this:

protected virtual void OnNameChanged()
{
    // If there are registered clients raise event
    if (NameChanged != null)
        NameChanged(this, new EventArgs());
}

Microsoft then adds: "The derived class can choose not to call the base class during the processing of OnEventName. Be prepared for this by not including any processing in the OnEventName method that is required for the base class to work correctly."

Therein lies the problem. In general, OnNameChanged() may do some default processing before it raises the event. An OnNameChanged() override may want to do something different. But to ensure that external clients work properly it must call the base class version. If it doesn't call the base class version the event will not be raised for external clients. And it may forget to call the base class version. Forgetting to call the base class version, which raises the event, violates the Liskov (polymorphic) substitution principle: methods that use references to base classes must be able to use objects of derived classes without knowing it. Fortunately, there is a way out of this problem.

The Template Method Design Pattern

The purpose of the Template Method design pattern is to define an algorithm as a fixed sequence of steps but have one or more of the steps variable. In our example the algorithm can be considered to consist of raising the event and responding to it. The part that needs to be variable is the response. So the trick is to separate this from the raising of the event. We split OnNameChanged() into two methods: InternalOnNameChanged() and OnNameChanged(). InternalOnNameChanged() calls OnNameChanged() to perform default processing and then raises the event.

private void InternalOnNameChanged()
{
    // Derived classes may override default behaviour
    OnNameChanged();

    // If there are registered clients raise event
    if (NameChanged != null)
        NameChanged(this, new EventArgs());
}

protected virtual void OnNameChanged()
{
    // Implement default behaviour here
}

The Name property is now altered to look like this:

get { return name; }
set { name = value; InternalOnNameChanged(); }

The advantages of this technique are:

  1. An essential step in the base class implementation, in this case raising the event, cannot be avoided by the derived class's failing to call the base class implementation. So external clients can be reliably serviced.
  2. The derived class can safely replace the base class's default behaviour in OnNameChanged() with no worries.

Example - Template Method Design Pattern Event Handling

Below is a complete example implemented in both C# and Visual Basic .NET. It consists of three classes, Supplier, ExternalClient and InternalClient. Supplier raises an event. The two client classes each consume the event. InternalClient is a derived class of Supplier.

ExternalClient contains an embedded Supplier reference. However, this is initialised with an InternalClient reference. Thus when ExternalClient registers for the Supplier event, this invokes InternalClient's OnNameChanged() override. Then the event is handled by InternalClient's NameChanged() and finally ExternalClient's NameChanged() handlers.

So the output that is produced is:

InternalClient.OnNameChanged
InternalClient.NameChanged
ExternalClient.NameChanged

C# Implementation

using System;

class Test
{
    static void Main(string[] args)
    {
        ExternalClient client = new ExternalClient();
        client.TestSupplier();
    }
}

// Generates an event when its property is set.
public class Supplier
{
    public Supplier() {}

    public event EventHandler NameChanged;

    public string Name
    {
        get { return name; }
        set { name = value; InternalOnNameChanged(); }
    }

    // Internal clients, i.e., derived classes, can override
    // default behaviour.
    protected virtual void OnNameChanged()
    {
        // Implement default behaviour here
        Console.WriteLine("Supplier.OnNameChanged");
    }

    // If internal clients (derived classes) override the
    // default behaviour in OnNameChanged
    // then external clients will still receive the event.
    private void InternalOnNameChanged()
    {
        // Derived classes may override default behaviour
        OnNameChanged();

        // If there are registered clients raise event
        if (NameChanged != null)
            NameChanged(this, new EventArgs());
    }

    private string name;
}

// An "internal" client that handles the Supplier.NameChanged
// eventbut first overrides its default behaviour.
public class InternalClient : Supplier
{
    public InternalClient()
    {
        NameChanged += new EventHandler(this.Supplier_NameChanged);
    }

    protected override void OnNameChanged()
    {
        // Override default behaviour of Supplier.NameChanged
        Console.WriteLine("InternalClient.OnNameChanged");
    }

    private void Supplier_NameChanged(object sender, EventArgs e)
    {
        // Handle Supplier.NameChanged
        Console.WriteLine("InternalClient.NameChanged");
    }
}

// An "external" client that handles the
// Supplier.NameChanged event.
public class ExternalClient
{
    public ExternalClient()
    {
        // Instantiate supplier as a reference to an
        // InternalClient instance.
        // This should trigger the InternalClient.OnNameChanged
        // override when an event is raised.
        supplier = new InternalClient();
        supplier.NameChanged +=
                new EventHandler(this.supplier_NameChanged);
    }

    public void TestSupplier()
    {
        // This should raise an event and it will be handled by
        // the InternalClient and ExternalClient handlers.
        supplier.Name = "Kevin McFarlane";
    }

    private void supplier_NameChanged(object sender, EventArgs e)
    {
        // Handle Supplier.NameChanged
        Console.WriteLine("ExternalClient.NameChanged");
    }

    private Supplier supplier;
}

Visual Basic .NET Implementation

Module Test

    Sub Main()
        Dim client As ExternalClient = New ExternalClient()
        client.TestSupplier()
    End Sub

End Module

' Generates an event when its property is set.
Public Class Supplier
    Sub New()
    End Sub

    Public Event NameChanged As EventHandler

    Public Property Name() As String
        Get
            Return mName
        End Get
        Set(ByVal Value As String)
            mName = Value
            InternalOnNameChanged()
        End Set
    End Property

    ' Internal clients, i.e., derived classes, can override
    ' default behaviour.
    Protected Overridable Sub OnNameChanged()
        ' Implement default behaviour here
        Console.WriteLine("Supplier.OnNameChanged")
    End Sub

    Private Sub InternalOnNameChanged()
        ' Derived classes may override default behaviour
        OnNameChanged()

        ' Raise event for clients
        RaiseEvent NameChanged(Me, New EventArgs())
    End Sub

    Private mName As String
End Class

' An "internal" client that handles the Supplier.NameChanged
' event but first overrides its default behaviour.
Public Class InternalClient
    Inherits Supplier

    Sub New()
    End Sub

    Protected Overrides Sub OnNameChanged()
        ' Override default behaviour of Supplier.NameChanged
        Console.WriteLine("InternalClient.OnNameChanged")

    End Sub

    Private Sub Supplier_NameChanged(ByVal sender As Object, _
        ByVal e As EventArgs) _
        Handles MyBase.NameChanged

        ' Handle Supplier.NameChanged
        Console.WriteLine("InternalClient.NameChanged")
    End Sub
End Class

' An "external" client that handles the
' Supplier.NameChanged event.
Public Class ExternalClient
    Sub New()
        ' Instantiate Supplier as a reference to an
        ' InternalClient instance.
        ' This should trigger the InternalClient.OnNameChanged
        ' override when an event is raised.
        mSupplier = New InternalClient()

        ' Register for Supplier.NameChanged event
        AddHandler mSupplier.NameChanged, _
                   AddressOf mSupplier_NameChanged
    End Sub

    Public Sub TestSupplier()
        ' This should raise an event and it will be handled by
        ' the InternalClient and ExternalClient handlers.
        mSupplier.Name = "Kevin McFarlane"

    End Sub

    Private Sub mSupplier_NameChanged(ByVal sender As Object, _
                                      ByVal e As EventArgs)
        ' Handle Supplier.NameChanged
        Console.WriteLine("ExternalClient.NameChanged")
    End Sub

    Private mSupplier As Supplier
End Class

Tools:
Add www.codeguru.com to your favorites
Add www.codeguru.com to your browser search box
IE 7 | Firefox 2.0 | Firefox 1.5.x
Receive news via our XML/RSS feed







RATE THIS ARTICLE:   Excellent  Very Good  Average  Below Average  Poor  

(You must be signed in to rank an article. Not a member? Click here to register)

Latest Comments:
Small glitch in your implementation of Template method pattern - Legacy CodeGuru (06/24/2003)
Visual Studio.NET - Legacy CodeGuru (03/22/2002)

View All Comments
Add a Comment:
Title:
Comment:
Pre-Formatted: Check this if you want the text to display with the formatting as typed (good for source code)



(You must be signed in to comment on an article. Not a member? Click here to register)

internet.comearthweb.comDevx.commediabistro.comGraphics.com

Search:

Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

Jupitermedia Corporate Info

Legal Notices, Licensing, Reprints, Permissions, Privacy Policy.
Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers

Whitepapers and eBooks

Intel Whitepaper: Comparing Two- and Four-Socket Platforms for Server Virtualization
IBM Solutions Brief: Go Green With IBM System xTM And Intel
HP eBook: Simplifying SQL Server Management
IBM Contest: Are You the Next Superstar? Join the "Search for the XML Superstar" Contest to Find Out
Microsoft PDF: Top 10 Reasons to Move to Server Virtualization with Hyper-V
Microsoft PDF: Six Reasons Why Microsoft's Hyper-V Will Overtake Vmware
Microsoft Step-by-Step Guide: Hyper-V and Failover Clustering
Intel PDF: Quad-Core Impacts More Than the Data Center
Intel PDF: Virtualization Delivers Data Center Efficiency
Go Parallel Article: PDC 2008 in Review
Microsoft PDF: Top 11 Reasons to Upgrade to Windows Server 2008
Avaya Article: Communication-Enabled Mashups: Empowering Both Business Owners and IT
Intel Whitepaper: Building a Real-World Model to Assess Virtualization Platforms
  PDF: Intel Centrino Duo Processor Technology with Intel Core2 Duo Processor
Microsoft Article: Build and Run Virtual Machines with Hyper-V Server 2008
Go Parallel Article: Q&A with a TBB Junkie
IBM Whitepaper: Innovative Collaboration to Advance Your Business
Internet.com eBook: Real Life Rails
IBM eBook: The Pros and Cons of Outsourcing
Internet.com eBook: Best Practices for Developing a Web Site
IBM CXO Whitepaper: The 2008 Global CEO Study "The Enterprise of the Future"
Avaya Article: Call Control XML in Action - A CCXML Auto Attendant
IBM CXO Whitepaper: Unlocking the DNA of the Adaptable Workforce--The Global Human Capital Study 2008
Adobe Acrobat Connect Pro: Web Conferencing and eLearning Whitepapers
HP eBook: Guide to Storage Networking
MORE WHITEPAPERS, EBOOKS, AND ARTICLES