Events and Delegates - Part I

by agrace 1. September 2007 09:46

This is part one of a series of posts on events and delegates, a powerful means of implementing loosely-coupled designs. This part will focus on delegates. You can read part two here.

Introduction

As a developer, I think it took me longer than necessary to get my head around the code details of the event/delegate paradigm. In retrospect, I believe this was mainly due to my lack of understanding of the delegate type, which connects everything together.

Another problem I encountered was a lack of consistency of language and definitions used by the authors of books and articles on events and delegates. More on this later.

Publishers + Subscribers = Observer Pattern

 

There is no need to have a PhD in anonymous methods, function pointers, multicast versus single cast delegates and the like, in order to get started with events. What you do need is a basic understanding of the central piece of the jigsaw: the delegate. Next, you need to get an overview of the Publish-Subscribe model. Finally, you need to open up your code editor and create a basic event sample for yourself.

Overview

Generally, delegates (or callbacks) are typesafe pointers to methods, similar to function pointers in C++. We can use delegates to specify callbacks and also to register handlers for GUI events like button clicks. Unlike function pointers, which can only contain the address of a static method, a delegate can refer to instance methods in classes - this is what makes them tremendously useful. A delegate is like an interface: the difference is that the delegate declares a single static or reference method, whereas the interface can declare several methods. Whenever we want classes to communicate with less coupling than with an interface, we should use a delegate. In other words, delegates are not only for use with events!

If we need to specify an action or method in advance and we don't know which method, or even object, that will perform this action, we should use a delegate. For example, we can connect a button to a delegate and at runtime we can resolve that delegate to a particular method to be run.

Delegates

A delegate is a special type in .NET whose main role in life is to hold a reference to a method. Delegates are defined with a signature, much like methods, and can only hold a reference to a method which matches this signature. We declare a delegate by using the "delegate" keyword and specifying the signature of the method to which it holds a reference. The following is a declaration of a delegate that can reference a method that takes two parameters and does not return a value:

public delegate void EventHandler(object sender, EventArgs e);

 

The delegate is a TYPE; think of the entire statement above as a type. A method matching this signature would look like this:

public void Button_Clicked(object sender, EventArgs e);

 

In the following example, the delegate called SampleDelegate can invoke any method that is passed a string parameter and returns an int:

delegate int SampleDelegate(sting str);

 

We can now initialize a new delegate, like so...

SampleDelegate del = new SampleDelegate(DoSomething);

 

 ... and can call the delegate as follows:

del(someString);

 

Terminology

A final point regarding terminology. When we talk about classes, we use two separate terms: class and object. With delegates, there is only a single term. An instance of a delegate class is also known as a delegate. So, we need to pay attention to context here. I will come back to the issue of terminology in the next post which will focus on events.

Tags: ,

ASP.NET | C#




Most people are by now familiar with the typical usage of the Eval() and Bind() methods in a GridView. You can even use a format string with the Eval() method:

<%# Eval("[Email]", "mailto:{0}") %>

 

However, sometimes you may want to obfuscate an email address when displaying a contact link in a GridView. This example displays a databased contact link in the GridView only if there is an email address in the database for a particular business.

GridView Business Listing

 

When the column is read-only, the Eval() one-way binding method is the most appropriate choice:

 <ItemTemplate>
   <table cellpadding="5" cellspacing="10" >
   <tr>
     <td style="padding-left:10px;">
       <span class="formtext"><b><%# Eval("BizName")%></b></span><br />
       <span class="formtext">
         Address: <%# Eval("Street")%>,&#160;<%# Eval("City")%>
       </span><br />
       <span class="formtext">Phone: <%# Eval("Phone")%></span><br />
       <span class="formtext">
         Email: <%# BuildContactRequest((int)Eval("BizID"), (string)Eval("Email")) %>
       </span><br />
       <span class="formtext">Website:<a href='<%# Eval("BizURL") %>'
        target="_blank"> <%# Eval("BizURL") %></a></span><br />
     </td>
   </tr>
   </table>
 </ItemTemplate>

 // Code-behind: URL with query parameter is returned
 protected string BuildContactRequest(int bizId, string email)
 {
   string contactURL = "";

   // Check if email is blank
   if (email == "")
   {
     return "";
   }
   // Contruct the Contact URL with the BizID query parameter
   else
   {
     contactURL += "<a href=Contact.aspx?bizParam=" + bizId + ">Contact Us</a>";
     return contactURL;
   }
 }


When the user clicks on the "Contact Us" link, they are directed to a Contact form which displays the recipient business name and generates an email to that business's (confidential) email address.

Contact Form

 

The business ID is passed as a query parameter and used to retrieve the business email address:

  if (Request.QueryString["bizParam"] != null)
  {
    bizId = Convert.ToInt32(Request.QueryString["bizParam"]);
    DataSet ds = new DataSet();

    // Get email address from DB and store it in session state
    ds = mbidBiz.GetContactDetailsByBizID(bizId);

    contactLabel.Visible = true;
    contactLabel.Text = "Contact: " + ds.Tables[0].Rows[0]["BizName"].ToString();

    Session["Email"] = ds.Tables[0].Rows[0]["Email"].ToString();
  }


If security is a real concern, rather than passing a query parameter, you could alternatively store and retrieve it from session state. Many small businesses today do not have their own website and may be using personal email addresses. The above example is based on an actual website I developed recently. Enjoy!

 




I recently had to implement a pair of cascading DropDownLists for an ASP.NET Web project and found very little workable code samples on Google. Here's the code that worked for me.

The second DropDownList is enabled based on the selection made in the first and the cascade works in both directions, depending on the selection. The tricky bit was getting the index of the selection in the second drop down. I eventually found samples of the IndexOf and FindByText ListItemCollection methods on the MSDN site.

    <asp:DropDownList ID="InstrumentDDL" AutoPostBack="true"  
        OnSelectedIndexChanged="InstrumentDDL_SelectedIndexChanged"
            Width="100" runat="server">
        <asp:ListItem Text="Select" Value="Select"></asp:ListItem>
        <asp:ListItem Text="Guitar" Value="Guitar"></asp:ListItem>
        <asp:ListItem Text="Mandolin" Value="Mandolin"></asp:ListItem>
    </asp:DropDownList>

    <asp:DropDownList ID="GuitarDDL" AutoPostBack="true"  
        OnSelectedIndexChanged="GuitarDDL_SelectedIndexChanged"
            Enabled="false" Width="100" runat="server">
        <asp:ListItem Text="Select" Value="Select"></asp:ListItem>
        <asp:ListItem Text="Fender" Value="Fender"></asp:ListItem>
        <asp:ListItem Text="Gibson" Value="Gibson"></asp:ListItem>
        <asp:ListItem Text="Gretsch" Value="Gretsch"></asp:ListItem>
        <asp:ListItem Text="Martin" Value="Martin"></asp:ListItem>
    </asp:DropDownList>

    protected void InstrumentDDL_SelectedIndexChanged(object sender, EventArgs e)
    {
        // Reset GuitarDDL
        GuitarDDL.SelectedIndex = -1;

        // Dynamically enable the GuitarDDL based on InstrumentDDL selection
        if (InstrumentDDL.SelectedValue == "Guitar")
        {
            GuitarDDL.SelectedItem.Text = "Select";
            GuitarDDL.Enabled = true;
        }
        else
        {
            GuitarDDL.SelectedItem.Text = GuitarDDL.SelectedValue;
            GuitarDDL.Enabled = false;
        }
    }

    protected void GuitarDDL_SelectedIndexChanged(object sender, EventArgs e)
    {
        // Every menu item refers to "Guitar" in the InstrumentDDL menu (except "Select")
        if (GuitarDDL.SelectedValue == "Select")
        {
            // Reset InstrumentDDL menu and disable the GuitarDDL menu
            InstrumentDDL.SelectedIndex
                = InstrumentDDL.Items.IndexOf(InstrumentDDL.Items.FindByText("Select"));
            GuitarDDL.Enabled = false;
        }
        else
        {
            InstrumentDDL.SelectedIndex
                 = InstrumentDDL.Items.IndexOf(InstrumentDDL.Items.FindByText("Guitar"));
        }
    }