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);
}
}
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;
}
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);
}
}
0 comments: