- Warning.
- Introduction.
- Observable property design pattern in .Net.
- Memory leaks.
- Partial solution number 1.
- Partial solution number 2.
- Weak delegates.
- Weak delegate decorators factory.
- Further improvements.
- Sample projects.
- UnsubscribeTest sample project.
- WinFormsIdleTest sample project.
- ConsoleTest sample project.
- Lessons learnt.
- Conclusion.
- License.
- Links.
- History.
While reading this article you should keep in mind that I rather freely
operate some terms and use:
- event source;
- observable;
- model
as synonyms and:
- event sink;
- event listener;
- observer
also as synonyms. Although in general it's not true, in the context of the
article I assume it is.
One of the things that still excites me most in Microsoft's .Net Framework is
the delegates. They make a programmers life a lot easier when it comes to
implementing various callbacks and notifications.
I have some experience in Java (though a bit outdated) and I remember my
frustration over the Java Beans event model. To make an object notify listeners
about the object's state changes you have to declare an event callback
interface, implement it in the listeners and keep track of them somehow. While
designing of the interface you have to decide how many callback methods should
the interface declare because if later on your decide to add a couple more
notifications you'll have to create another callback interface and, possibly,
rewrite all your listeners. At times like that I had been recalling good old C
function pointers. And then Microsoft introduced the delegates.
A delegate is essentially a type-safe object-oriented method pointer. A
delegate can be singlecast, thus it points at a single method of a single object
(very good for callbacks), or multicast thus pointing at many methods of various
objects or even at many methods of a single object (very good for event
notifications).
Although delegates are classes, you can do a little without help of the
compiler. You can declare new delegate types with special syntax, create
delegate instances, assign, add and subtract them and you can invoke them in
turn invoking their target methods. You cannot subclass delegates (they all are
sealed). But to tell the truth - do you really need it?
The delegates take crucial part in notification scenarios. For instance, you
can notify some object that a value of a property has been changed. Microsoft in
the documentation on Windows Forms programming suggests following pattern for
controls and components:
- Declare a property with getter and setter; for instance,
Value
property:
object _value;
public object Value
{
get
{
return _value;
}
}
- Declare an event with type
EventHandler
and name
ValueChanged
that gets raised when the value of the property changes:
public event EventHandler ValueChanged;
- Write a protected method
OnValueChanged
that raises the
event:
protected virtual void OnValueChanged(EventArgs e)
{
EventHandler handler = ValueChanged;
if(null != handler)
handler(this, e);
}
- Write a proper setter code:
set
{
if(_value != value)
{
_value = value;
OnValueChanged(EventArg.Empty);
}
}
Although this code seems to be verbose, in more complex scenarios this
verbosity pays off, because the pattern is very flexible. Windows Forms library
extensively uses it to notify the forms about their child controls state
changes. For instance, the Control
class has a property Font
and a respective event FontChanged
. When the font changes, the form
and other listeners subscribed for the event become aware of the change.
You can view a property and it's change event as a single entity very similar
to the Smalltalk's ValueModel
class but a bit more effective
because a class may have many independent observable properties. If you utilize
the pattern in your own code, it would help you to separate the data and it's
presentation. If your data object has a public property Salary
of
type decimal
and an event SalaryChanged
of type
EventHandler
, you can manually "bind" the property to a text box on the
form: the form itself subscribes for the
SalaryChanged
event of the
data object and for the
TextChanged
event of the text box and
passes/converts the values from the UI to the data object and vice versa. So if
the data object changes the value of the
Salary
property, the value
in the text box gets changed accordingly, and if the user enters a new salary,
the value of the
Salary
property also changes. I'm talking about
rather old but very powerful and still usable pattern named
Model-View-Controller or MVC for short. The model here is our property with the
event, the view is the text box and the controller is the form, or it's two
event handlers, to be precise.
The good thing is that you can bind the the property/event pairs and UI
elements with the standard data binding (NB: it is more oriented on IList
and DataSet
-based data objects) or with Marc Clifton's "simplified
data binding" (see
http://www.codeproject.com/csharp/simpledatabinding.asp [^]).
I've been using a set of classes similar to Marc's BindHelper
but a
bit simpler and with optional designer support. Also I usually declare for my
Windows Forms programs a global singleton object Options
that keeps
all the program options (who could've imagine). All or most properties of the
Options
object implement the pattern. It gives me ability to open
up an options form that "binds" itself to the properties and lets the user to
edit them on the fly. Moreover, because other forms of the program are also
bound to the Options
object, many things in the interface change
immediately, so the user can "play" with the applications settings and see the
changes right away. Such options form do not require OK, Cancel or
Apply buttons, only a Revert button that discards all the changes
and return everything to the previous state (this implemented with use of
a GoF [^]
design pattern, Memento
[^]).
Applications that utilize this approach don't feel like Windows applications,
but they are definitely convenient.
Also note that in the System.ComponentModel
namespace you find a
very useful delegate type CancelEventHandler
with accompanying
CancelEventArgs
argument class. With it you can create
constrained properties, the properties with values that cannot be changed at
certain circumstances. To make the Value
property constrained, you
need to declare another event, ValueChanging
of type
CancelEventHandler
and fire it before actual value assignment. If any of
the event listeners changes the value of the
Cancel
property of the
argument object, the setter should leave the value and return.
But you have to pay for everything, especially for comfort. While this "live
update" scenario is very nice and convenient, there is one annoying thing - you
have to track all your event subscriptions. If your form registers a handler for
ValueChanged
event, you have to unregister it at some point,
because delegates are strong references to the event sinks and if the event
source outlives the event sink, the latter stays in memory. Even in a middle
size application the subscriptions can be hard to trace and you might just
forget to unsubscribe from an event (it is so-called "split cleaner" error
pattern).
The sad thing is that you may really get used to the fact that CLR manages
memory. The code generated by UI designers never unregisters event handlers. All
this might make you think that the delegates work in "fire and forget" fashion.
Suppose you have an event source object that exposes a lot of events and many
event sink objects that subscribes for many of them. When the event source goes
out of scope, it is a good idea to unsubscribe all event sinks at once. It can
easily be done. If you event is declared like:
public event EventHandler PropertyChanged;
you should change this declaration to:
EventHandler propertyChanged;
public event EventHandler PropertyChanged
{
add
{
propertyChanged += value;
}
remove
{
propertyChanged -= value;
}
}
Why would you do that? Because if some objects have subscribed for the
PropertyChanged
event you can unsubscribe them at once by just assigning
null
to the delegate storage field
propertyChanged
:
public void Unsubscribe()
{
propertyChanged = null;
}
Simple, isn't it?
In case you use co-called optimized event implementation with the
EventHandlerList
class in the
Unsubscribe()
method you may
just recreate the event handler list, unsubscribing from all the events at once.
Event simpler.
Suppose the scenario above doesn't suite you needs and you have to
unsubscribe only one observer from all the events exposed by the observable.
Let's the event source manage the removal.
First, we need a simple utility class:
public sealed class DelegateUtil
{
. . . . .
public static Delegate Unsubscribe(Delegate del, object target)
{
if(null == del)
return null;
if(null == target)
return del;
Delegate[] list = del.GetInvocationList();
for(int i = 0; i < list.Length; i++)
{
if(target == list[i].Target)
list[i] = null;
}
return Delegate.Combine(list);
}
. . . . .
}
The Unsubscribe()
method removes all delegates that point at any
method of the target
object from the multicast delegate del
.
I.e. when a particular event sink target
goes out of scope the
event source removes all references to it from the event delegate. Let me show
how to use this snippet using a class that declares our Value
property:
public void Unsubscribe(object target)
{
Value = (EventHandler)DelegateUtil.Unsubscribe(Value, target);
}
If you class exposes other events, repeat DelegateUtil.Unsubscribe
for every event. Keep in mind that the actual code depends on the event
implementation (see EventHandlerList
class description in .Net
Framework SDK documentation for details), but it works. We have replaced a bunch
of -=
operators with a single call.
If the event sink object does not know exactly when it goes out of scope,
i.e. it is neither a control, nor a component, nor it implements the
IDisposable
interface, the tricks above won't work and the sink object
will stay in memory. This wouldn't be the case if the delegates were actually
weak references. You wouldn't need to unsubscribe from the events at all. But
had Microsoft really made delegates weak, we would have a lot of horrible time
debugging managed callbacks for various unmanaged APIs. It would be nice if some
day Microsoft adds weak delegates support to .Net but only as additional
feature.
But is it possible to somehow emulate weak delegates at all? Of course!
Greg Schechter in his blog presents an implementation of classes very close
to weak delegates:
http://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx [^].
Actually I highly recommend to read his post, he describes the problem with
event delegates very thoroughly and with great diagrams that make the picture
much clearer.
He introduces an object that decorates a delegate making it behave as if it
was a weak one (Greg calls it the WeakContainer
). The decorator
receives the notifications from the observable on behalf of the observer and
keeps a weak reference to the observer. When the observer goes out of scope and
gets garbage collected, the decorator unsubscribes itself from the notifications
and becomes eligible for the garbage collection itself. The observable knows
nothing about it or the fact that it sends the notifications to an intermediate
object instead of the observer.
Ian Griffit in his blog answers to Greg's call and tries to implements a
generic weak delegate:
http://www.interact-sw.co.uk/iangblog/2004/06/06/weakeventhandler [^].
The problem with his code is that he keeps a weak reference to a usual delegate,
so the delegate and the observer object get garbage collected too soon.
Xavier Musy offers a working solution:
http://www.seedindustries.com/blog/x/2004_06_01_archive.html [^].
He essentially reimplements the MULticastDelegate
class using a
weak reference instead of usual reference for the Target
property.
The only bad thing is that he uses the MethodInfo.Invoke
method
which is painfULly slow. While it might be of little concern for an average
Windows Forms application, in some cases like mULtiple views receiving
notifications from the same model it might be inappropriate.
In my opinion, Greg Schechter's solution is almost perfect. The only problem
with it is that the decorator must know too much about both the observable and
the observer. It has to know at least their types, the event and the event
handler. So you cannot make it generic, even in .Net 2.0, and have to
reimplement it for each event of each event source class. But if you take a
close look at the Greg's code you'll notice that the implementation of the
decorator is simple, if not trivial. The finalizer there is just for sample, so
you need a constructor and the method with signature identical to the target
method. If it is that simple, it would be easy enough to create such objects on
the fly. Do you remember the word "thunk"?
Enter System.Reflection.Emit
and WeakDelegateDecorator
.
The methods of the latter class perform the actual code generation using the
former namespace. But before we start actually code the IL emitting, let's
consider possible event wiring scenarios:
- Instance event is wired to an instance event handler;
- Instance event is wired to a static event handler;
- Static event is wired to a instance event handler and
- Static event is wired to a static event handler.
In the scenarios 2 and 4 it doesn't matter what kind of delegate we are
using, the delegate target lives as long as the application domain lives. So we
basically need to consider scenarios 1 and 3. The scenario no. 1 is related to
the MVC pattern and data binding, the scenario no. 3 is very well known to
anyone who ever (mis)used the Application.Idle
event.
Let's imagine that we have an event source object with property Name
and event NameChanged
, and an event sink object with the
method OnNameChanged
with proper signature, of course, that
gets called when the NameChanged
event is raised. Let's write a
weak event decorator class for such scenario:
public class WeakInstanceToInstance: WeakReference
{
public WeakInstanceToInstance(EventSink observer):
base(observer)
{}
public void Handler(object sender, EventArgs e)
{
EventSink observer = (EventSink)Target;
if(null != observer)
observer.OnEvent(sender, e);
else
{
EventSource observable = sender as EventSource;
if(null != observable)
observable.Event -= new EventHandler(Handler);
}
}
}
This class or more specific, it's translation to IL instructions, is going to
be our template for scenario 1.
Let's imagine how an object, probably a form, gets wired to
Application.Idle
event. The object would have a method like
OnIdle
and the weak decorator would look like:
public class WeakStaticToInstance: WeakReference
{
public WeakStaticToInstance(EventSink observer):
base(observer)
{}
public void Handler(object sender, EventArgs e)
{
EventSink observer = (EventSink)Target;
if(null != observer)
observer.OnIdle(sender, e);
else
EventSource.StaticEvent -= new EventHandler(Handler);
}
}
The code for scenario 3 is a bit simpler but they share a lot.
While Microsoft uses and recommends to use for events delegates signatures
two parameters, the first of type object
and the second derived
from EventArgs
class, there is no restriction on number of
parameters for an event delegate and there are exceptions. Take a look at
AbstractMargin.cs file from SharpDevelop IDE source code
(src\Libraries\ICSharpCode.TextEditor\src\Gui\AbstractMargin.cs). I have to take
this into account. I also should remember that event delegates might return
results. If these results are reference types, everything is easy, we should
return null
, for some value types built-in to the MSIL I may use
built-in instructions, and for the value types in general I should use the
default constructors that create uninitialized structures.
Given proper info, the overloaded method Decorate
generates the
proper weak decorators for specified events and returns a delegate that should
be wired to the source event:
public static Delegate Decorate(Type eventSourceType, string eventName,
Delegate del)
public static Delegate Decorate(object eventSource, string eventName,
Delegate del)
public static Delegate Decorate(object eventSource, string eventName,
object eventSink, string eventHandlerName)
public static Delegate Decorate(Type eventSourceType, string eventName,
object eventSink, string eventHandlerName)
public static Delegate Decorate(EventInfo eventInfo, Delegate del)
public static Delegate Decorate(EventInfo eventInfo, object eventSink,
string eventHandlerName)
You usually decorate event delegates by replacing code like:
eventSourceObject.Event +=
new SomeEventHandler(eventSinkObject.EventHandlerMethod);
with:
eventSourceObject.Event +=
(SomeEventHandler)WeakDelegateDecorator.Decorate(eventSourceObject,
"Event", eventSinkObject, "EventHandlerMethod");
or with more type safe (and memory consuming) version:
eventSourceObject.Event +=
(SomeEventHandler)WeakDelegateDecorator.Decorate(eventSourceObject,
"Event", new SomeEventHandler(eventSinkObject.EventHandlerMethod));
I wanted to make the WeakDelegateDecorator
class generic for
.Net 2.0 so some Decorate
overloads would return typed delegates,
but unfortunately C# 2.0 does not allow to specify the Delegate
as
the base class for generic constraints. So we have to live with these type
casts.
These Decorate
methods check parameters and call the private method
InternalDecorate
. The method first tries to find already registered weak
decorator type in it's internal cache and only if the combination of it's
parameters is a new, it creates a new weak decorator class and keeps it's type
for later use.
Keep in mind that the event handler methods must be public.
This is because in .Net a class that resides in another assembly cannot
access non-public memebers of your class. It is a limitation of the
weak delegates and I'm not aware of any workaround.
Each decorator class has two methods - a constructor and an event handler.
The constructor method is simple, it just passes the reference the the
event sink object to the base class (the WeakReference
)
constructor. But for the handler method I use various optimization techniques
for non-trivial delegates. For instance, the MSIL has special instruction that
reference the first four arguments of the method. Because our handler is the
instance method, the very first parameter with index 0 is the this
pointer and I use ldarg 0 instruction to put it to the stack. For the
most common case the sender and the event arguments object have 1 and 2 indices.
If the handler has more than 3 arguments, I use ldarg.s {index}
instructions. There is also ldarg {index} instruction, but it is for the
methods with more that 256 arguments. I believe that a person who tries to
write methods with this many arguments should not be allowed to use a computer.
I add the tailcall prefix to some call/callvirt
instructions: to the call to the event target method and to the
remove_{Event}()
call if the method does not return value. In both cases
the original code returns immediately; the tail call optimization effectively
"reuses the stack" of our handler method. For x86 processors the JIT
should
replace
call/
leave instruction pair with simple
jmp.
It definitely makes the decorator faster in most common cases. On one of
my test computer (with Intel CPU and Windows 2000) it gives the code good
performance boost, on another (AMD 64 and Windows XP) doesn't change the picture.
However, according to this blog post:
http://blogs.msdn.com/shrib/archive/2005/01/25/360370.aspx [
^]
by Shri Borde, tail calls in .Net may cause problems so may I'll have to remove
this optimization in the long run.
Yet another optimization is use of predefined MSIL instructions for basic
value types. For a generic value types, even for low-level IntPtr
and UIntPtr
types (they mapped to MSIL as native int
and native uint
repectively) I use the default constructors.
I have changed the Builder
class to loosen security restrictions
for the decorators - essentially I've made the handlers public. Now you can use
the Pvax.WeakDelegates.dll assembly in applications that run in the
LocalIntranet security zone (i.e. run from a UNC share), not only in
FullTrust zone.
My implementation of weak decorators differs from Greg's. Greg's
containers/observers keep references to "weak containers" and attach events of
many observables to a single mediator that relays the events to the observer
(I've redrawn Greg's diagram):
In my implementation the observes and the observables do not know about
mediators at all so I have to create a new decorator class for each new
event/handler combination:
Greg's approach is better in terms of memory consumption.
I considered to use .Net 2.0 dynamic methods as a lightweight replacement for
the decorators, but it seems to me that they don't fit in the picture. Please,
prove me wrong, it'd be nice to get rid of this clunky in-memory type cache. If
you ask me why I don't use generic hash tables, I have two answers. First, the
code is written for .Net 1.1. Second, just imagine yourself the declaration of
the cache with generics.
I've introduced a set of new tests that prove the weak delegate decorators
work for .Net 2.0 generic delegatess. I had been worrying a bit that I'd have
to change or tweak the IL code generation for generics somehow. To my surprise
the code written for .Net 1.1 worked with generics without any change. It means
that Microsoft's developers had done a amaising piece of job seamlessly
integrating generics into CTS and CLR. Excellent!
By the way, after writing tests for generic delegates I don't consider
declarations of reflection-based factory methods ugly: declarations and casts
for more or less complex generic delegates are uglier.
My code generates a lot of types, it would be a good idea to generate as many
event handlers per decorator as possible to reduce the number of types and the
total number of decorators.
My code is inherently not thread safe; it would be nice to make it so.
If the IL code generator throws an InvalidProgramException
, it
means that I screwed up something in the IL code (not that I think I did but it
might happen). This is unrecoverable exception because the in-memory assembly
that gathers all the decorator types becomes corrupt. I do not catch this
exception and let it propagate up to the global exceptions handler. If this
happens, the most reasonable way is to let the application terminate and notify
me. If you have access to the application's source code, please end me at least
the declaration of the delegate which invocation causes the error.
As usual, my sample project is created in SharpDevelop IDE. The project
contains five subprojects: Pvax.WeakDelegates, Pvax.WeakDelegates.Tests,
WinFormsIdleTest, ConsoleTest and UnsubscribeTest. I've also converted them to
VS-compatible projects with #D 2.0.
The first two are the weak decorators library itself and the tests assembly
for the library. The rest are described below.
The sample project folder also contains FxCop and NUnit project files. FxCop
fairly says that the Pvax.WeakDelegates namespace contains too little classes,
but keep in mind that all decorators are emitted in this namespace also. It also
is not happy about static constructors and ToString()
calls without
specifying the culture info, but all these are "implementation details", so just
ignore them. I created a separate NUnit project because I somehow screwed up my
#D installation and the unit testing add-in doesn't work anymore. If the
compiler complains about missing reference to the NUnit.Framework
assembly, remove the reference, add it again and recompile the test project.
This sample consOLe application illustrates the DelegateUtil
class usage. It creates an observable instance and some observers, changes a
property of the observable, unsubscribes one observer unconditionally with the
DelegateUtil
and changes the property one more time. All
notifications are logged to the consOLe to make sure that the observer has
really been unsubscribed.
The WinFormsIdleTest is a sample of the weak delegates usage in a Windows
Forms application. I hook child forms to the Application.Idle
event. Although the forms get disposed of at the WM_CLOSE
message
handlers, the memory occupied by them stays reachable (i.e. not eligible
for garbage collection) because of unsubscribed events. If you decorate the
event delegates with the weak decorators, the memory gets reclaimed.
The sample application consists of two forms. The main form:
has three buttons. By clicking the New sticky form... button you
create a small form:
It performs a simple animation using Application.OnIdle
event
(NB: never use this event for that purpose in real applications, it is highly
ineffective). The Click
event handler for this button uses usual
+=
operator to subscribe the child from for the idle event. You can
close the child form with the standard Close button (the small button
with diagonal cross in the right side of the window's caption).
If you look at the code, you'll see that the child form constructor allocates
an array of bytes to Simulate the real application's data. I allocate a 100
Kbytes from the large heap, because the memory manager considers objects that
big "large". It is very convenient to watch these allocations using the
Performance Monitor application:
Add, for instance, following counters:
- # Bytes in all Heaps, scale 0.00001:
- Large Object Heap Size, scale 0.00001:
If while running the program, you should create a couple dozens of child
forms with the New sticky form... button, you should see a picture
similar to:
Now close all the child forms. Their respective Dispose()
methods will release window handles associated with the forms, but because all
these forms have strong references to them from the static event, they stay in
memory. And, of course, the byte blocks they own also stay in memory. Click
Force GC! button to make the GC to reclaim all unused memory (NB: never do
anything like that in a real program). You'll see a picture in the
Performance Monitor similar to:
I've marked with light green line the moment I clicked the button. As you
see, the size of the large heap doesn't change and we obviously have a memory
leak here.
Restart the sample application and repeat all the steps except this time
click the New weal sticky form... button. The event handler for this
button is almost the same, but it uses my WeakDelegateDecorator
class. The Performance Monitor graph after allocating a fair number of
the child forms should look like:
Now close all the child forms and click Force GC! button. After that
the graph should look like:
Again, I've marked with light green line the moment I clicked the button. As
you can see, with weak event decorators the memory gets reclaimed as it should
be.
This sample project illustrates the property observer and constrained
property patterns. It tests a way to unsubscribe all the observers from the
events at once. It works because the Observable
class instance
outlives the instances of the Observer
class.
It also tests the weak delegates decorators. An interesting detail: at weak
decorators tests you are going to see that while the event sink objects get
garbage collected, the decorators themselves stay in memory up until the moment
when the code raises the events again. Only at that moment the decorators become
aware that their targets are not reachable anymore and unsubscribe themselves
from the source events. This tests show how much slower the decorated delegates
are in comparison with undecorated.
You can play with various constants like HogSize
,
MemoryNoiseObjectCount
,
ObserversCount
and
PropertyChangeCount
. For instance, the
MemoryNoiseObjectCount
constant defines how many "memory noise" objects should be allocated by the
GenerateMemoryNoise()
method. This method Simulates a real-world
memory manager load. By changing the constants you will get different results,
there's one thing that doesn't change - the weak decorators eat additional
memory and make the delegate calls slower, so use them wisely.
It is interesting to run the ConsoleTest under
CLR Profiler [^].
To minimize the memory stress, set the MemoryNoiseObjectCount
to 0.
If you do so, you'll see an enormous number of delegates generated by the
application. This is because +=
and -=
operators are
rather ineffective in mass event subscription scenarios, so if you are going to
subscribe a lot of object for an event at once, use the overload of the
Delegate.Combine()
method that takes an array of delegates as it's
parameter. On the other hand, it shows effectiveness of the .Net garbage
cOLlector because despite the fact that subscription generates a lot of small
objects, the time that the subscription takes depends almost linearly on the
number of the subscribers.
Because the delegates are immutable, using +=
and -=
operations on them repeatedly generate a vast amount of small objects
(singlecast delegates) that have big chances to be promoted to heap generation 1
and even generation 2. In other hand, null
is a valid delegate
object. That's why my DelegateUtil.Unsubscribe()
method just
nullifies the slots in the original delegate's invocation list and uses the
Delegate.Combine()
method: this trick minimizes the number of
intermediate objects during the process of the new delegate creation.
MSIL and System.Reflection.Emit
at first glance look scary but
in practice are even simpler than System.CodeDom
. The virtual
machine that "executes" the IL code is a simple stack machine, and the
intermediate language itself reminds me of Forth so I feel at home here. I'm not
quite sure about really big projects but in this particular one it took me only
two days to write the IL emitting code that worked. Anyway, thanks to Microsoft
that made this great feature available for an average programmer.
You know it, but I repeat - unit tests are useful. They helped me to find and
squash two nasty bugs in the IL code generator. Both bugs were related to value
objects that the decorators' event handlers had to return.
Delegates are great, no doubt. Their use makes the source code clearer and
object models of our applications more flexible. There is, however, a flip side
of this coin. We cannot get rid of it, but we can simplify our tasks with
DelegateUtil
class and weak delegate decorators.
The DelegateUtils
class is covered by BSD license (see the
source code). Because the weak delegates decorator factory code is based on Greg
Shechter's sources and ideas and because I borrowed some from MSDN, I put the
Pvax.WeakDelegates
assembly and it's source under MIT license. Use
it, however, on your own risk, I haven't tested it in all possible scenarios.
- 11/01/2006 - initial version;
- 11/13/2006 - tail call optimization added; the
Builder
class
code changed to loosen permission restrictions - now the decorators' factory
works in the LocalIntranet security zone.
- 4/12/2006 - .Net 2.0 project options fixed; differences in the resource
handling between #D versions eliminated;
INotifyPropertyChanges
and EventHandler<T>
compatibility ensured for WeakDelegateDecorator
; internal methods
and objects refactored a bit; added two new overloads for the Decorate
method.
- 14/01/2007 - generic delegates compatibility ensured; project layouts are
changed slightly; a new subprojects has been added to test bxb error report.