Click here to Skip to main content
Email Password   helpLost your password?

Table of contents

Introduction

When using normal C# events, registering an event handler creates a strong reference from the event source to the listening object.

NormalEvent.png

If the source object has a longer lifetime than the listener, and the listener doesn't need the events anymore when there are no other references to it, using normal .NET events causes a memory leak: the source object holds listener objects in memory that should be garbage collected.

There are lots of different approaches to this problem. This article will explain some of them and discuss their advantages and disadvantages. I have sorted the approaches in two categories: first, we will assume that the event source is an existing class with a normal C# event; after that, we will allow modifying the event source to allow different approaches.

What Exactly are Events?

Many programmers think events are a list of delegates - that's simply wrong. Delegates themselves have the ability to be "multi-cast":

EventHandler eh = Method1;
eh += Method2;

So, what then are events? Basically, they are like properties: they encapsulate a delegate field and restrict access to it. A public delegate field (or a public delegate property) could mean that other objects could clear the list of event handlers, or raise the event - but we want only the object defining the event to be able to do that.

Properties essentially are a pair of get/set-methods. Events are just a pair of add/remove-methods.

public event EventHandler MyEvent {
   add { ... }
   remove { ... }
}

Only adding and removing handlers is public. Other classes cannot request the list of handlers, cannot clear the list, or cannot call the event.

Now, what leads to confusion sometimes is that C# has a short-hand syntax:

public event EventHandler MyEvent;

This expands to:

private EventHandler _MyEvent; // the underlying field
// this isn't actually named "_MyEvent" but also "MyEvent",
// but then you couldn't see the difference between the field
// and the event.
public event EventHandler MyEvent {
  add { lock(this) { _MyEvent += value; } }
  remove { lock(this) { _MyEvent -= value; } }
}

Yes, the default C# events are locking on this! You can verify this with a disassembler - the add and remove methods are decorated with [MethodImpl(MethodImplOptions.Synchronized)], which is equivalent to locking on this.

Registering and deregistering events is thread-safe. However, raising the event in a thread-safe manner is left to the programmer writing the code that raises the event, and often gets done incorrectly: the raising code that's probably used the most is not thread-safe:

if (MyEvent != null)
   MyEvent(this, EventArgs.Empty);
   // can crash with a NullReferenceException
   // when the last event handler is removed concurrently.

The second most commonly seen strategy is first reading the event delegate into a local variable.

EventHandler eh = MyEvent;
if (eh != null) eh(this, EventArgs.Empty);

Is this thread-safe? Answer: it depends. According to the memory model in the C# specification, this is not thread-safe. The JIT compiler is allowed to eliminate the local variable, see Understand the Impact of Low-Lock Techniques in Multithreaded Apps [^]. However, the Microsoft .NET runtime has a stronger memory model (starting with version 2.0), and there, that code is thread-safe. It happens to be also thread-safe in Microsoft .NET 1.0 and 1.1, but that's an undocumented implementation detail.

A correct solution, according to the ECMA specification, would have to move the assignment to the local variable into a lock(this) block or use a volatile field to store the delegate.

EventHandler eh;
lock (this) { eh = MyEvent; }
if (eh != null) eh(this, EventArgs.Empty);

This means we'll have to distinguish between events that are thread-safe and events that are not thread-safe.

Part 1: Listener-side Weak Events

In this part, we'll assume the event is a normal C# event (strong references to event handlers), and any cleanup will have to be done on the listening side.

Solution 0: Just Deregister

void RegisterEvent()
{
    eventSource.Event += OnEvent;
}
void DeregisterEvent()
{
    eventSource.Event -= OnEvent;
}
void OnEvent(object sender, EventArgs e)
{
    ...
}

Simple and effective, this is what you should use when possible. But, often, it's not trivially possible to ensure the DeregisterEvent method is called whenever the object is no longer in use. You might try the Dispose pattern, though that's usually meant for unmanaged resources. A finalizer will not work: the garbage collector won't call it because the event source still holds a reference to our object!

Advantages

Simple if the object already has a notion of being disposed.

Disadvantages

Explicit memory management is hard, code can forget to call Dispose.

Solution 1: Deregister When the Event is Called

void RegisterEvent()
{
    eventSource.Event += OnEvent;
}

void OnEvent(object sender, EventArgs e)
{
    if (!InUse) {
        eventSource.Event -= OnEvent;
        return;
    }
    ...
}

Now, we don't require that someone tells us when the listener is no longer in use: it just checks this itself when the event is called. However, if we cannot use solution 0, then usually, it's also not possible to determine "InUse" from within the listener object. And given that you are reading this article, you've probably come across one of those cases.

But, this "solution" already has an important disadvantage over solution 0: if the event is never fired, then we'll leak listener objects. Imagine that lots of objects register to a static "SettingsChanged" event - all these objects cannot be garbage collected until a setting is changed - which might never happen in the program's lifetime.

Advantages

None.

Disadvantages

Leaks when the event never fires; usually, "InUse" cannot be easily determined.

Solution 2: Wrapper with Weak Reference

This solution is nearly identical to the previous, except that we move the event handling code into a wrapper class that forwards the calls to a listener instance which is referenced with a weak reference. This weak reference allows for easy detection if the listener is still alive.

WeakEventWithWrapper.png

EventWrapper ew;
void RegisterEvent()
{
    ew = new EventWrapper(eventSource, this);
}
void OnEvent(object sender, EventArgs e)
{
    ...
}
sealed class EventWrapper
{
    SourceObject eventSource;
    WeakReference wr;
    public EventWrapper(SourceObject eventSource,
                        ListenerObject obj) {
        this.eventSource = eventSource;
        this.wr = new WeakReference(obj);
        eventSource.Event += OnEvent;
   }
   void OnEvent(object sender, EventArgs e)
   {
        ListenerObject obj = (ListenerObject)wr.Target;
        if (obj != null)
            obj.OnEvent(sender, e);
        else
            Deregister();
    }
    public void Deregister()
    {
        eventSource.Event -= OnEvent;
    }
}

Advantages

Allows garbage collection of the listener object.

Disadvantages

Leaks the wrapper instance when the event never fires; writing a wrapper class for each event handler is a lot of repetitive code.

Solution 3: Deregister in Finalizer

Note that we stored a reference to the EventWrapper and had a public Deregister method. We can add a finalizer to the listener and use that to deregister from the event.

~ListenerObject() {
    ew.Deregister();
}

That should take care of our memory leak, but it comes at a cost: finalizable objects are expensive for the garbage collector. When there are no references to the listener object (except for the weak reference), it'll survive the first garbage collection (and move to a higher generation), have the finalizer run, and then can only be collected after the next garbage collection (of the new generation).

Also, finalizers run on the finalizer thread; this may cause problems if registering/deregistering events on an event source is not thread-safe. Remember, the default events generated by the C# compiler are not thread-safe!

Advantages

Allows garbage collection of the listener object; does not leak wrapper instances.

Disadvantages

Finalizer delays GC of listener; requires thread-safe event source; lots of repetitive code.

Solution 4: Reusable Wrapper

The code download contains a reusable version of the wrapper class. It works by taking the lambda expressions for the code parts that need to be adapted to a specific use: Register event handler, deregister event handler, forward the event to a private method.

eventWrapper = WeakEventHandler.Register(
    eventSource,
    (s, eh) => s.Event += eh, // registering code
    (s, eh) => s.Event -= eh, // deregistering code
    this, // event listener
    (me, sender, args) => me.OnEvent(sender, args) // forwarding code
);

WrapperWithLambdas.png

The returned eventWrapper exposes a single public method: Deregister. Now, we need to be careful with lambda expressions, since they are compiled to delegates that may contain further object references. That's why the event listener is passed back as "me". Had we written (me, sender, args) => this.OnEvent(sender, args), the lambda expression would have captured the "this" variable, causing a closure object to be generated. Since the WeakEventHandler stores a reference to the forwarding delegate, this would have caused a strong reference from the wrapper to the listener. Fortunately, it's possible to check whether a delegate captures any variables: the compiler will generate an instance method for lambda expressions that capture variables, and a static method for lambda expressions that don't. WeakEventHandler checks this using Delegate.Method.IsStatic, and will throw an exception if you use it incorrectly.

This approach is fairly reusable, but it still requires a wrapper class for each delegate type. While you can get pretty far with System.EventHandler and System.EventHandler<T>, you might want to automate this when there are lots of different delegate types. This could be done at compile-time using code generation, or at runtime using System.Reflection.Emit.

Advantages

Allows garbage collection of listener object; code overhead not too bad.

Disadvantages

Leaks wrapper instance when event never fires.

Solution 5: WeakEventManager

WPF has built-in support for listener-side weak events, using the WeakEventManager class. It works similar to the previous wrapper solutions, except that a single WeakEventManager instance serves as a wrapper between multiple sender and multiple listeners. Due to this single instance, the WeakEventManager can avoid the leak when the event is never called: registering another event on a WeakEventManager can trigger a clean-up of old events. These clean-ups are scheduled using the WPF dispatcher, they will occur only on threads running a WPF message loop.

Also, the WeakEventManager has a restriction that our previous solutions didn't have: it requires the sender parameter to be set correctly. If you use it to attach to button.Click, only events with sender==button will be delivered. Some event implementations may simply attach the handlers to another event:

public event EventHandler Event {
    add { anotherObject.Event += value; }
    remove { anotherObject.Event -= value; }
}

Such events cannot be used with WeakEventManager.

There is one WeakEventManager class per event, each with an instance per thread. The recommended pattern for defining these events is a lot of boilerplate code: see "WeakEvent Patterns" on MSDN [^].

Fortunately, we can simplify this with Generics:

public sealed class ButtonClickEventManager
    : WeakEventManagerBase<ButtonClickEventManager, Button>
{
    protected override void StartListening(Button source)
    {
        source.Click += DeliverEvent;
    }

    protected override void StopListening(Button source)
    {
        source.Click -= DeliverEvent;
    }
}

Note that DeliverEvent takes (object, EventArgs), whereas the Click event provides (object, RoutedEventArgs). While there is no conversion between delegate types, C# supports contravariance when creating delegates from method groups [^].

Advantages

Allows garbage collection of listener object; does not leak wrapper instances.

Disadvantages

Tied to a WPF dispatcher, cannot be easily used on non-UI-threads.

Part 2: Source-side Weak Events

Here, we'll take a look at ways to implement weak events by modifying the event source.

All these have a common advantage over the listener-side weak events: we can easily make registering/deregistering handlers thread-safe.

Solution 0: Interface

The WeakEventManager also deserves to be mentioned in this section: as a wrapper, it attaches ("listening-side") to normal C# events, but it also provides ("source-side") a weak event to clients.

In the WeakEventManager, this is the IWeakEventListener interface. The listening object implements an interface, and the source simply has a weak reference to the listener and calls the interface method.

SourceWithListeners.png

Advantages

Simple and effective.

Disadvantages

When a listener handles multiple events, you end up with lots of conditions in the HandleWeakEvent method to filter on event type and on event source.

Solution 1: WeakReference to Delegate

This is another approach to weak events used in WPF: CommandManager.InvalidateRequery looks like a normal .NET event, but it isn't. It holds only a weak reference to the delegate, so registering to that static event does not cause memory leaks.

WeakDelegateBug.png

This is a simple solution, but it's easy for event consumers to forget about it and get it wrong:

CommandManager.InvalidateRequery += OnInvalidateRequery;

//or

CommandManager.InvalidateRequery += new EventHandler(OnInvalidateRequery);

The problem here is that the CommandManager only holds a weak reference to the delegate, and the listener doesn't hold any reference to it. So, on the next GC run, the delegate will be garbage collected, and OnInvalidateRequery doesn't get called anymore even if the listener object is still in use. To ensure the delegate survives long enough, the listener is responsible for keeping a reference to it.

WeakDelegates.png

class Listener {
    EventHandler strongReferenceToDelegate;
    public void RegisterForEvent()
    {
        strongReferenceToDelegate = new EventHandler(OnInvalidateRequery);
        CommandManager.InvalidateRequery += strongReferenceToDelegate;
    }
    void OnInvalidateRequery(...) {...}
}

WeakReferenceToDelegate in the source-code download shows an example event implementation that is thread-safe and cleans the handler list when another handler is added.

Advantages

Doesn't leak delegate instances.

Disadvantages

Easy to get wrong: forgetting the strong reference to the delegate causes events to fire only until the next garbage collection. This can result in hard-to-find bugs.

Solution 2: object + Forwarder

While solution 0 was adapted from the WeakEventManager, this solution is adapted from the WeakEventHandler wrapper: register an object,ForwarderDelegate pair.

SmartEventForwarding.png

eventSource.AddHandler(this,
    (me, sender, args) => ((ListenerObject)me).OnEvent(sender, args));

Advantages

Simple and effective.

Disadvantages

Unusual signature for registering events; forwarding lambda expressions require cast.

Solution 3: SmartWeakEvent

The SmartWeakEvent in the source code download provides an event that looks like a normal .NET event, but keeps weak references to the event listener. It does not suffer from the "must keep reference to delegate"-problem.

void RegisterEvent()
{
    eventSource.Event += OnEvent;
}
void OnEvent(object sender, EventArgs e)
{
    ...
}

Event definition:

SmartWeakEvent<EventHandler> _event
   = new SmartWeakEvent<EventHandler>();

public event EventHandler Event {
    add { _event.Add(value); }
    remove { _event.Remove(value); }
}

public void RaiseEvent()
{
    _event.Raise(this, EventArgs.Empty);
}

How does it work? Using the Delegate.Target and Delegate.Method properties, each delegate is split up into a target (stored as a weak reference) and the MethodInfo. When the event is raised, the method is invoked using Reflection.

SmartEventReflection.png

A possible problem here is that someone might try to attach an anonymous method as an event handler that captures a variable.

int localVariable = 42;
eventSource.Event += delegate { Console.WriteLine(localVariable); };

In this case, the delegate's target object is the closure, which can be immediately collected because there are no other references to it. However, the SmartWeakEvent can detect this case and will throw an exception, so you won't have any difficulty to debug problems because the event handler is deregistered before you think it should be.

if (d.Method.DeclaringType.GetCustomAttributes(
  typeof(CompilerGeneratedAttribute), false).Length != 0)
    throw new ArgumentException(...);

Advantages

Looks like a real weak event; nearly no code overhead.

Disadvantages

Invocation using Reflection is slow; does not work in partial trust because it uses reflection on private methods.

Solution 4: FastSmartWeakEvent

The functionality and usage is identical to the SmartWeakEvent, but the performance is dramatically improved.

Here are the benchmark results of an event with two registered delegates (one instance method and one static method):

Normal (strong) event...   16948785 calls per second
Smart weak event...           91960 calls per second
Fast smart weak event...    4901840 calls per second

How does it work? We're not using Reflection anymore to call the method. Instead, we're compiling a forwarder method (similar to the "forwarding code" in the previous solutions) at runtime using System.Reflection.Emit.DynamicMethod.

Advantages

Looks like a real weak event; nearly no code overhead.

Disadvantages

Does not work in partial trust because it uses reflection on private methods.

Suggestions

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralThread safety
dbonne
5:05 2 Feb '10  
Hi

I am using (an adapted version) of the FastSmartWeakEvent.

The problem with FastSmartWeakEvent, for thread safety, is that the Raise method both does a copy, and the execution of the delegates. If I want to make it thread-safe I would need to put a lock around the whole Raise method. This might lead to unexpected results as the code that gets executed is not known, and it might result in a performance problem if the code if the callback takes a long time.

I adapted FastSmartWeakEvent:
* introduced a IRaisable interface with the Raise method therein.
* added a CreateRaisable() method in FastSmartWeakEvent returning an IRaisable object (containing the copy of the events). CreateRaisable removes any dead entries.
* removed the original Raise method.

Now I can do:

IRaisable r;
lock(eventlock)
r = fswe.CreateRaisable();
r.Raise(); // outside of lock.

thanks for a great article and code!
Dirk
GeneralThorough description of alternate approaches
ToolmakerSteve2
11:41 13 Nov '09  
I found this article to be amazingly informative! Taught me a lot about the different approaches discussed.
Generalwell done
Xmen
10:26 8 Mar '09  
this is really best one...5(+) from me

TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L
%^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2
W_AD`EPABIKRDFVS)EVLQK)JKSQXUFYK[M`UKs*$GwU#(QDXBER@CBN%
Rs0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-i’TV.C\y<pŠjxsg-b$f4ia>
--------------------------------------------------------
128 bit encrypted signature, crack if you can

GeneralRe: well done
w395115323
18:35 26 Aug '09  
up...
Generalmethod access exception @ ee.TargetMethod.Invoke(target, parameters);
codemonkeyman2
18:54 5 Mar '09  
so i see in your sample that your event handler is private yet no exception.
In my case i get the method access exception. if i change it to public it works. Any thoughts?
GeneralRe: method access exception @ ee.TargetMethod.Invoke(target, parameters);
codemonkeyman2
7:07 6 Mar '09  
I thing worth mentioning this only happens in Silverlight. not a wpf app. my calling private methods in silverlight is different from in wpf?
GeneralRe: method access exception @ ee.TargetMethod.Invoke(target, parameters);
codemonkeyman2
7:55 6 Mar '09  
little more research led me to this article:
http://blogs.infosupport.com/blogs/willemm/archive/2009/02/02/Accessing-private-methods-in-silverlight.aspx
check it out, though i couldn't make head and tails as to how to incorporate that into your solution
GeneralRe: method access exception // type safety issues with FastSmartWeakEvent
Daniel Grunwald
8:23 6 Mar '09  
Reflection on private methods is only allowed with FullTrust, so you can't use it with Silverlight.
Also, the generated IL code in FastSmartWeakEvent is unverifiable (and thus requires FullTrust): there's no cast instruction to convert from object (the return type of WeakReference.get_Target) to the type required by the method call. It's actually type safe because we know the target method can be called on the instance stored in the delegate, but .NET cannot verify this without a runtime cast.

Actually, the version in this article punches a hole in the type system: there's no type check in the second parameter.
class EventArgs1 : EventArgs { public float Num = 1;
class EventArgs2 : EventArgs { public int Num; }
...
FastSmartWeakEvent<EventHandler<EventArgs2>> fswe = new FastSmartWeakEvent<EventHandler<EventArgs2>>();
fswe.Add((sender, e) => Console.WriteLine(e.Num.ToString()));
fswe.Raise(null, new EventArgs1());

This compiles and runs, and prints 1065353216 (the bits of 1.0f interpreted as int).

Unfortunately, I don't see a way to fix in the current FastSmartWeakEvent class without having a runtime check on every invocation.
The problem is that the FastSmartWeakEvent uses the delegate as type parameter, so it doesn't know the type derived from EventArgs until runtime.
A possible solution would be to change the class to always use System.EventHandler<T> (and maybe provide another version of the class for non-generic System.EventHandler).
Then we could make the type parameter just take the EventArgs type (FastSmartWeakEvent<EventArgs2>) and declare the Raise method to accept that type.

With the existing class as here on code project, callers of the Raise method must be careful to pass the correct EventArgs type. If you use the wrong type, you'll get undefined results, just like a good old unsafe pointer cast in C. (except that the GC can cause even more trouble if it can't tell integers from references anymore)

Hopefully I'll find time this weekend to update the article.

But that won't help you for Silverlight, as without FullTrust, .NET will reject all code that the JIT cannot prove to be typesafe - and not only the missing casts are problematic, it won't accept calls to private methods either.
GeneralReference
jpbochi
16:58 5 Mar '09  
I made a project using your code and posted on my blog. Check it out here[^].

I included your source code to download from there too. I hope I'm not breaking your code license. Poke tongue

5 stars to you post! Thumbs Up
GeneralGreat work
xcvsdfsdf
22:02 18 Feb '09  
This is the best article I have read, ever.
QuestionBug?
Fintan
5:14 5 Feb '09  
In the weak event classes static constructors you have code like the following:

if (argsParameter.ParameterType.IsSubclassOf(typeof(EventArgs)))
throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");


Shouldn't that be:
if (!argsParameter.ParameterType.IsSubclassOf(typeof(EventArgs)))
throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");


Great job on the article and thank you for taking the time to write it.
AnswerRe: Bug?
Daniel Grunwald
11:52 6 Feb '09  
You're right, that's a bug. My version accepts only System.EventArgs and the non-EventArgs classes that I wanted to reject, it fails with derived EventArgs.

The negated version works with derived EventArgs, but fails with System.EventArgs itself.
The correct fix would be:
if (!(typeof(EventArgs).IsAssignableFrom(argsParameter.ParameterType)))
throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");

GeneralGreat article + question
Ajek
11:53 30 Jan '09  
Detailed explanation

You mention that other object could clear the delegates invocationlist (in the beginning of the article, when explaining the differences between events and delegates).
I was just wondering how you could do that?

Best regards,
Ike
GeneralRe: Great article + question
Daniel Grunwald
11:59 30 Jan '09  
You cannot change the invocation list of an existing delegate; whenever you add or remove a handler, you are creating a new delegate with the new invocation list.

Assume you have a delegate, not an event:
public EventHandler MyEvent;

Then you can clear it by setting it to null:
someObject.MyEvent = null;

Events allow changing the underlying delegate field by adding or removing event handlers, but they prevent other changes to the field (like setting to null).
GeneralRe: Great article + question
Ajek
0:39 31 Jan '09  
Thanks for the answer.
I didn't realize it would be that easy.
GeneralGreat article
Donsw
8:51 25 Jan '09  
This is a very good article congraulations. good research.
GeneralAccess to modified closure
Paul Hatcher
1:42 4 Dec '08  
Resharper complains about the code in MakeDeregisterCodeAndWeakEventHandler as you are assigning the eventHandler to weh.deregisterCode, but then changing it.

EventHandler<TEventArgs> eventHandler = null;

weh.deregisterCode = delegate {
deregisterEvent(senderObject, eventHandler);
};

eventHandler = (sender, args) => {
TEventListener listeningObject = (TEventListener)weh.listeningReference.Target;
if (listeningObject != null) {
forwarderAction(listeningObject, sender, args);
} else {
weh.Deregister();
}
};
return eventHandler;
This means by the time it is invoked the eventHandler passed to deregisterCode will have your new definition - is this what was intended, or has Resharper's parser become confused?

Paul

GeneralSorry mate
Sacha Barber
1:31 25 Nov '08  
Daniel

I was truly sorry to beat you in the recent C# monthly competition, this article was/is great. Sorry mate. Keep up the great work though.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralGreat Article!!
Camilo Sánchez Herrera
17:39 23 Nov '08  
I was developing an application that was leaking lots of memory, and after many hours of googling I found out it was because of the Events I was using, and I took the approach of deregistering 'em.

Too bad I didn't found your article back then, it could have save me lots of time.

Anyway great article.
QuestionQuestion
Angel Kafazov
2:19 16 Nov '08  
Hi, just wondering, what software did you use for creating the graphics?
AnswerRe: Question
Daniel Grunwald
3:12 16 Nov '08  
I used PowerPoint 2007 - the boxes are just text boxes with one of the default "Quick styles".
However, some dotted arrows looked bad due to the anti-aliasing, so I had to edit them manually using Paint.NET.
GeneralWeakEventHandler compiles only under .NET 3.5
Uwe Keim
22:50 15 Nov '08  
Since I needed it for 2.0, I added my own Action and replaced yours by mine:

public delegate void ZetaAction();

public delegate void ZetaAction<T1, T2>(
T1 arg1,
T2 arg2
);

public delegate void ZetaAction<T1, T2, T3>(
T1 arg1,
T2 arg2,
T3 arg3
);


My personal 24/7 webcam - Always live Wink
Zeta Producer Desktop CMS - Intuitive, completely easy-to-use CMS for Windows.
Zeta Helpdesk - Open Source ticket software for Windows and web.
Zeta Uploader - Easily send large files by e-mail. Windows and web client.


GeneralRe: WeakEventHandler compiles only under .NET 3.5
Rainer Schuster
6:48 19 Nov '08  
How about a compiler switch determining the Target-Framework? Wouldn't name it ZetaAction, rather put it in a namespace ZETA, so you can use yours or the one frome the framework.

____________________________________________

Supported German .NET Resources:
http://dotnet-forum.de | http://dotnet-snippets.de
____________________________________________

GeneralWell thought out and succinct. [modified]
PSUA
2:07 14 Nov '08  
Can find no faults in your article. Well Done!

modified on Friday, November 14, 2008 7:42 AM

GeneralGood Information
Douglas Troy
7:27 11 Nov '08  
Daniel -

I just got around to reading this, and I have to say the information you provided was really good. I liked the layout, especially your advantages/disadvantages to each implementation. I've yet to review the source code, so my remarks speak only to your article, but I can't imagine it varies that greatly.

The only thing I would like to see added, is a list of references. Those articles/places were you read/gathered your information from, and/or other sources you recommend on this subject.

Regardless of that one minor point, you got my 5.

Thank you for sharing!



Last Updated 25 Apr 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010