Exam 70-562 - Custom controls with custom events

Filed under: , , by:

If you study the books I mentioned in my previous post you will find there how to design custom UserControl, WebControl, CompositeControl, CompositeDataBoundControl. You will learn there how to use some of their methods like EnsureChildren, CreateChildControls, ReCreateChildControls, PerformDataBinding, or EnsureDataBound. But when it comes to adding custom events to your custom control those books mention only 1 way - by declaring public event as a field in your class. And I think there's only a few developers that know the other way, and building custom controls is something that web developer does on a daily basis or at least very often. So what's the secret alternative? It is using System.ComponentModel.EventHandlerList class. And it is interesting that this class has been in .NET since version 1.1 and it's not very common - maybe because there is no examples in MSDN. So what's so interesting about this class? Let's declare 3 custom controls: without any events, with events are public fields and with EventHandlerList:

public class CustomDetailsView1 : WebControl
{
public override void RenderControl(System.Web.UI.HtmlTextWriter writer)
{
base.RenderControl(writer);
}
}
public class CustomDetailsView2 : WebControl
{
public event DetailsViewCommandEventHandler ItemCommand;
public event EventHandler ItemCreated;
public event DetailsViewDeletedEventHandler ItemDeleted;
public event DetailsViewDeletEventHandler ItemDeleting;
public event DetailsViewInsertedEventHandler ItemInserted;
public event DetailsViewInsertEventHandler ItemInserting;
public event DetailsViewUpdatedEventHandler ItemUpdated;
public event DetailsViewUpdateEventHandler ItemUpdating;
public event EventHandler ModeChanged;
public event DetailsViewModeEventHandler ModeChanging;
public event EventHandler PageIndexChanged;
public event DetailsViewPageEventHandler PageIndexChanging;
public override void RenderControl(System.Web.UI.HtmlTextWriter writer)
{
base.RenderControl(writer);
}
}
public class CustomDetailsView3 : WebControl
{
protected EventHandlerList _events;
public event DetailsViewCommandEventHandler ItemCommand
{
add
{
if (_events == null)
_events = new EventHandlerList();
_events.AddHandler("ItemCommand", value);
}
remove
{
if (_events != null)
_events.RemoveHandler("ItemCommand", value);
}
}
public event EventHandler ItemCreated
{
add
{
if (_events == null)
_events = new EventHandlerList();
_events.AddHandler("ItemCreated", value);
}
remove
{
if (_events != null)
_events.RemoveHandler("ItemCreated", value);
}
}
...
...
public override void RenderControl(System.Web.UI.HtmlTextWriter writer)
{
base.RenderControl(writer);
}
}
As you see if a custom control has many events that are very rarely used, a solution with EventHandlerList guarantees that your custom control won't use space unless an EventHandler has been added. I have tested how efficient is the solution with EventHandler vs using events.
Here are my test scenarios:
static void CustomDetailsView1Test()
{
System.GC.Collect();
Console.WriteLine("Memory test of CustomDetailsView control with no custom events");
Console.WriteLine("Before instantiation: "+System.GC.GetTotalMemory(true));
CustomDetailsView1 ctl = new CustomDetailsView1();
System.GC.KeepAlive(ctl);
Console.WriteLine("After instantiation: "+System.GC.GetTotalMemory(true));
ctl.Dispose();
ctl = null;
}
static void CustomDetailsView2Test()
{
System.GC.Collect();
Console.WriteLine("Memory test of CustomDetailsView control with custom events as fields");
Console.WriteLine("Before instantiation: "+System.GC.GetTotalMemory(true));
CustomDetailsView2 ctl = new CustomDetailsView2();
System.GC.KeepAlive(ctl);
Console.WriteLine("After instantiation: "+System.GC.GetTotalMemory(true));
ctl.ItemCreated += new EventHandler(ctl_ItemCreated);
Console.WriteLine("After hooking up to ItemCreated event: "+System.GC.GetTotalMemory(true));
ctl.ItemDeleting += new DetailsViewDeletEventHandler(ctl_ItemDeleting);
Console.WriteLine("After hooking up to ItemDeleting event: "+System.GC.GetTotalMemory(true));
ctl.Dispose();
ctl = null;
}
static void CustomDetailsView3Test()
{
System.GC.Collect();
Console.WriteLine("Memory test of CustomDetailsView control with custom events as elements of EventHandlerList");
Console.WriteLine("Before instantiation: "+System.GC.GetTotalMemory(true));
CustomDetailsView3 ctl = new CustomDetailsView3();
System.GC.KeepAlive(ctl);
Console.WriteLine("After instantiation: "+System.GC.GetTotalMemory(true));
ctl.ItemCreated += new EventHandler(ctl_ItemCreated);
Console.WriteLine("After hooking up totemCreated event: "+System.GC.GetTotalMemory(true));
ctl.ItemDeleting += new DetailsViewDeletEventHandler(ctl_ItemDeleting);
Console.WriteLine("After hooking up to ItemDeleting event: "+System.GC.GetTotalMemory(true));
ctl.Dispose();
ctl = null;
}
In order to find out how much memory is used by each control I use System.GC.GetTotalMemory method True value of method argument to force full collection. This method retrieves total number of bytes allocated, therefore I can check how much memory is used by each control. CustomDetailsView1 control has no events so it gives information how much memory a control needs. CustomDetailsView2 control is the same as CustomDetailsView1 but it contains events as fields so our test case shows how much memory all events require. And last test case shows how much memory is required before hooking up to an event and afterwards so we can tell how efficient is using EventHandlerList when there is no events hooked up and when there are. Here are the results:

The results are somehow interesting. Our custom control without events requires 80 bytes, with all events: 128 bytes and with EventHandlerList without any events hooked up - 84 bytes. Great! But what happens if we hook up to two events. For control with events as fields it required 32 bytes to do that but for control using EventHandlerList it was 96 bytes plus additional 12 bytes to instantiate EventHandlerList object. In my example I used constant strings as keys to add or remove handler from a list but usually the EventHandlerList is used as follows:
public class CustomDetailsView : WebControl
{
protected EventHandlerList listEventDelegates = new EventHandlerList();
static readonly object itemDeletingEventKey = new object();
public event DetailsViewDeleteEventHandler ItemDeleting
{
add { listEventDelegates.AddHandler(itemDeletingEventKey, value); }
remove { listEventDelegates.RemoveHandler(itemDeletingEventKey, value); }
}
public void Delete()
{
if (listEventDelegates[itemDeletingEventKey] != null)
(listEventDelegates[itemDeletingEventKey] as DetailsViewDeleteEventHandler)(this, EventArgs.Empty);
}
}
In this example static read only object (being a key in the list) and EventHandlerList are created (even if no method has been hooked up to the event). This makes the things even worse and makes that EventHandlerList is not a very good solution to save some memory. The only benefit I could see is if there is a special business need to control adding or removing handler to your events, however even though this solution should be only considered as a last resort. Given that this topic is fairly advanced, I would presume that it would be covered more likely by 70-564 exam rather than 70-562, but I couldn't stop sharing it as I couldn't understand why none of my books mention about it. So if developers who study to 70-562 exam don't find it useful, then hopefully will developers who want to be more knowledgable about designing custom events in their custom controls and maybe when they encounter a solution using EventHandlerList, they will understand what are the drawbacks of using it, and they might replace it with event fields and save some memory that busy Web Server needs.

0 comments: