Weak events in C # (Weak events in C #)

Source: Internet
Author: User
Tags emit instance method volatile wrappers

(Original translation Article • Reprint please specify source: Http://blog.csdn.net/hulihui)

    • Original: Weak events in C #: Different Approaches to Weak events. by Daniel Grunwald.
    • Download Source code-15.5 KB
    • Translation Pre-order
    • Translation PostScript
Directory
    • Introduction
    • What exactly is an event?
    • Part 1th: The weak event of the listener (listener-side)
      • Solution 0: Simply log out
      • Solution 1: Log off after an event is called
      • Solution 2: Wrappers with weak references (WeakReference)
      • Solution 3: Logoff in Finalizer (Finalizer)
      • Solution 4: Reusable wrappers
      • Solution 5: Weak event Manager (WeakEventManager)
    • Part 2nd: Weak events for event source (source-side)
      • Solution 0: interface
      • Solution 1: Weak reference delegation
      • Solution 2: Object + forwarder (forwarder)
      • Solution 3: Intelligent Weak event (smartweakevent)
      • Solution 4: Fast Intelligent Weak event (fastsmartweakevent)
    • Suggestions

Translation Pre-order

The content of. NET 2.0 that this article covers includes: Delegates (delegate), events, strong references (strong reference), weak references (weak reference), finalizers (finalizer), Garbage collectors (garbage Collector), Closed-loop objects (Closure object), reflection (reflect), thread safety, memory leaks (leak), and so on. Further understanding requires several concepts of. NET 3.0/3.5/4.0: Weak events (weak event), weak event Manager (WeakEventManager), lambda expressions, dispatcher (dispatcher), and so on.

Introduction

When using normal C # event conditions, registering an event handler (handler) creates a strong reference from the event source to the Listener object.

Normal is used if the event source object has a longer lifetime than the listener object, and the event listener is not referenced by another object and no longer needs the event. NET event will cause a memory leak: The event source object keeps a reference to the listener object that should be reclaimed by garbage (garbage) in memory.

There are many different solutions to this type of problem. This article will explain some of these methods to explore their pros and cons. I divide these methods into two categories: first, we assume that the event source is a class with normal C # events, and then we allow the event source to be modified to accommodate different methods.

What exactly is an event?

Many programmers consider an event to be a list of delegates. This is completely wrong. In fact, entrust yourself with a "multicast" (multi-cast) Capability:

EventHandler eh = Method1;
EH + = METHOD2;

So, what is an event? Initially, they resemble properties: Encapsulates a delegate field and restricts its access. Typically, a public delegate field (or a public delegate property) means that other objects can purge an event handler or fire an event, and we only want the definition of the event to have this operational capability. Essentially, the attribute is a pair of get/set methods, and the event is a pair of add/remove methods.

Public event EventHandler MyEvent

Add {...}
Remove {...}
}

In the code above, only the addition and removal are public, other classes cannot request the execution of the handler list, the linked list cannot be purged, and the event cannot be called. The problem with this form is that C # event shorthand syntax sometimes causes confusion for programmers:

public event EventHandler MyEvent;

Further extended to the following conditions:

Private EventHandler _myevent;  underline the beginning of the field
It is not the actual name "_myevent", but "MyEvent",
So you can't distinguish between fields and events.

{
Add {Lock (this) {_myevent + = value;}}
Remove {Lock (this) {_myevent-= value;}}

It is worth noting that the default C # event is a lock on this, and you can use a disassembler (disassembler) To verify this: The Add/remove method marks the property [MethodImpl ( methodimploptions.synchronized)], which is equivalent to the lock on this. In this way, registration and logoff events are thread-safe. However, coding for firing events in a thread-safe manner is done by programmers, who tend to do the wrong thing-often the code that might be used is not thread-safe:

if (myevent! = null)

When the last event handler concurrently removes the
The system may crash when NullReferenceException.

The second common strategy is to read the event delegate to a local variable first:

EventHandler eh = myevent;
if (eh! = null) EH (this, eventargs.empty);

Is this thread-safe? Answer: still need to see. This is also not thread-safe, according to the memory model in the C # specification. The JIT compiler allows this local variable to be eliminated (see "Understanding low-lock technology impacts in multithreaded applications" (understand the impact of low-lock techniques in multithreaded apps). However, since the 2.0 version of the Microsoft. NET runtime has a stronger memory model, then the code is thread-safe. happen to be, at Microsoft. It is also thread-safe on NET1.0 and 1.1, but its implementation details are not documented in the relevant documentation.

According to the European Computer Manufacturers Association (ECMA) specification, a correct workaround is to move the local variable assignment statement to the lock (this) block, or use the volatile (volatile) field to save the delegate.

EventHandler eh; EventHandler;
Lock (this) {eh = MyEvent;}
if (eh! = null) EH (this, eventargs.empty);

Therefore, we have to differentiate between: thread-safe events, non-thread-safe events.

Part 1th: The weak event of the listener (listener-side)

In this section, it is assumed that the event is a normal C # event (a strong reference event handler) and that any cleanup work is done on the listener side.

Solution 0: Simply log out
void Registerevent ()
{
Eventsource.event + = OnEvent;

void Deregisterevent ()
{
Eventsource.event-= OnEvent

void OnEvent (object sender, EventArgs e)
{

}

The above is a simple and effective form that we often use. However, when an object is no longer in use, it is usually not ensured that the Deregisterevent method is called. You can try the Dispose pattern (which usually means unmanaged resources), but the finalizer (Finalizer) is not executed: The garbage collector does not invoke this finalizer because the event source still maintains a reference to the Listener Object!

Advantages:

If the object is already marked as disposed, it is simple (meaning that you can call Filalizer-the translator).

Disadvantages:

Explicit memory management is more difficult and may forget to call Dispose.

Solution 1: Log off after an event is called
void Registerevent ()
{
Eventsource.event + = OnEvent;
}

void OnEvent (object sender, EventArgs e)
{
if (! InUse) {
Eventsource.event-= OnEvent;
Return
}

}

Now, no one needs to point out when the listener is no longer in use: it only needs to check itself when the event is invoked. However, if we cannot use solution 0, it is often not possible to determine inuse from the listener. If you are reading this article, you may have already encountered one of these situations.

However, compared with solution 0, this "solution" already has a serious drawback: if the event is never fired (that is, OnEvent has never been called-the translator note), then the listener will also be leaked. Imagine this situation where many objects are registered to a static "settingschanged" event--all of these objects will not be garbage collected until a setting changes--This setting change may never occur during the lifetime of the program.

Advantages:

-

Disadvantages:

When an event has never been fired, a memory leak usually "InUse" is not easy to determine.

Solution 2: Wrappers with weak references (WeakReference)

The solution is almost identical to the previous one, except that we move the event-handling code into a wrapper class that forwards the listener instance to a weak reference (for the concept of weak references, refer to (weakreference)-translator note). This weak reference will be easily detected when the listener is alive.

Eventwrapper ew;
void Registerevent ()
{
ew = new Eventwrapper (EventSource, this);
}
void OnEvent (object sender, EventArgs e)
{

}
Sealed class Eventwrapper
{

WeakReference WR;
Public Eventwrapper (SourceObject eventSource, Listenerobject obj)

This.eventsource = EventSource;
THIS.WR = new WeakReference (obj); Create a weak reference to a listenerobj--the translator's note
Eventsource.event + = OnEvent;
}
void OnEvent (object sender, EventArgs e)
{
Listenerobject obj = (listenerobject) WR. Target; Get Listener Object--a translator's note
if (obj! = null)
Obj. OnEvent (sender, E);
Else
Deregister ();
}
public void deregister ()
{
Eventsource.event-= OnEvent;
}
}

Advantages:

Allow garbage collection listener objects.

Disadvantages:

When an event is never fired, a wrapper class will repeat a large amount of code for each event handler when it leaks the wrapper instance.

Solution 3: Logoff in Finalizer (Finalizer)

Note that a eventwrapper reference is stored in the above scenario, and there is a public method deregister that can add a finalizer (Finalizer) to the listener, which can invoke the wrapper's logoff method.

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

This scenario is a concern for memory leaks, but at a cost: for the garbage collector, the object can be terminated at a high cost. When the object reference is not being monitored (except for weak references), it will survive the first garbage collection and upgrade the generation. Assume that the finalizer is running and is recycled during the next second garbage collection (a new generation of objects). Also, the finalizer runs on the finalizer thread and can cause problems if the event source for the registration/logoff event is not thread-safe. Keep in mind that the default events produced by the C # compiler are not thread-safe!

Advantages:

Allows garbage collection to listen for objects and does not leak wrapper instances.

Disadvantages:

Finalizers delay GC Listeners, event sources that require thread safety, and large amounts of duplicate code.

Solution 4: Reusable wrappers

The download code contains a reusable wrapper class (Weakeventhandler) and uses lambda expressions to accommodate specific applications: Registering event handlers, Unregistering event handlers, and forwarding events to private methods.

Eventwrapper = Weakeventhandler.register (
EventSource,
(s, eh) = = s.event + = Eh,// registration code
(s, eh) = = s.event-Eh,// logoff code
This,// Event Monitor Listener
(Me, sender, args) = Me. OnEvent (sender, args)// Forwarding code
);

The returned Eventwrapper exposes a single public method: Deregister. Now, we must handle lambda expressions with care because they are compiled into delegates that might refer to other objects, which is why event listeners pass "Me". Suppose we write (me, sender, args) = this. OnEvent (sender, args), this lambda expression captures the "this" variable, resulting in a closed-loop object (Closure objects). Because Weakeventhandler stores a reference to a forwarding delegate, this results in a strong reference from the wrapper to the listener. Fortunately, it can check if a delegate captures any variables: The compiler will generate an instance method for the lambda expression that captures the variable, and a static method that does not capture the variable. Weakeventhandler uses Delegate.Method.IsStatic to check for this condition and throws an exception if it is used improperly.

This approach is highly reusable, but it still requires a wrapper class for each delegate type. When using System.EventHandler and system.eventhandler<t>, we may want to automate this work, especially when there are many different delegate types. This can be done at compile time using code generation, or at run time using System.Reflection.Emit.

Advantages:

Allow garbage to reclaim listening objects; code overhead is not too bad.

Disadvantages:

Leaks the wrapper instance when the event never fires.

Solution 5: Weak event Manager (WeakEventManager)

WPF's built-in WeakEventManager class supports listener weak events, similar to the previous wrapper solution, except that a single WeakEventManager instance acts as a wrapper between multiple senders and multiple listeners. Because it is a single instance, WeakEventManager avoids leaks when an event is never called: the cleanup of old events can be triggered when another event is registered on WeakEventManager. These cleanup is dispatched by the WPF Dispatcher (dispatcher) and runs on the WPF message loop thread.

Additionally, WeakEventManager has a limitation that the previous solution does not have: The sender parameters are required to be set correctly. Use it to attach a button. When click, only Sender==button events can be passed forward. Note that WeakEventManager does not apply to events of the following type: A simple attach handler to another event:

Public event EventHandler Event {
Add {anotherobject.event + = value;}
Remove {anotherobject.event-= value;}
}

Each event has a WeakEventManager class, one instance per thread. It is recommended to refer to a large boilerplate pattern code when defining such events: See "WeakEvent Mode" on MSDN (WeakEvent Patterns). Fortunately, we can use generics to simplify this work:

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 has a signature (object, EventArgs), while the Click event is provided (object, RoutedEventArgs). Although there is no conversion relationship between delegate types, C # supports contravariance when creating a delegate from a method group (contravariance when creating delegates by method groups).

Advantages:

Allows garbage collection to listen for objects without leaking wrapper instances.

Disadvantages:

Binds WPF dispatcher, which is not easy to use on non-UI threads.

Part 2nd: Weak events for event source (source-side)

Here you will explore various ways to modify event sources to implement weak events. Comparing the listener's weak events, all of these methods have a common advantage: a thread-safe registration/Logoff event handler can be made easier.

Solution 0: interface

This section also mentions WeakEventManager: as a wrapper, it attaches ("listening-side") to normal C # events and also provides ("Source-side") a weak event to the client. The IWeakEventListener interface is defined in the WeakEventManager, the listener implements the interface, and the event source only needs to have a listener weak reference and invoke the interface method.

Advantages:

Simple and effective.

Disadvantages:

When a listener handles multiple events, the Handleweakevent method is accompanied by a number of criteria for filtering event types and event sources.

Solution 1: Weak reference delegation

This is another way to handle weak events in WPF: Commandmanager.invalidaterequery looks like a normal. Net event, but that's not true: it keeps only weak references to delegates, and registering to this static event does not cause a memory leak.

While this is a simple solution, the event consumer is apt to forget to use and easily misuse:

Commandmanager.invalidaterequery + = Oninvalidaterequery;

Commandmanager.invalidaterequery + = new EventHandler (oninvalidaterequery);

The problem is that Commandmanager has only a weak reference to the delegate, and the listener does not reference it. Therefore, the next time the GC is run, the delegate will be garbage collected, and oninvalidaterequery can no longer be called, even if the listener is still in use. In order to ensure that the delegate survives long enough, the listener is responsible for maintaining a reference to it.

Class  Listener {
EventHandler strongreferencetodelegate;
public void Registerforevent ()
{
Strongreferencetodelegate = new EventHandler (oninvalidaterequery);
Commandmanager.invalidaterequery + = Strongreferencetodelegate;
}
void Oninvalidaterequery (...) {...}
}

The Weakreferencetodelegat in the download code gives an example of an event implementation that is thread-safe and clears the handler list when another handler is added.

Advantages:

The delegate instance is not compromised.

Disadvantages:

Easy misuse: Forgetting a strong reference to a delegate can cause bugs to find difficulties only when the event is fired on the next garbage collection.

Solution 2: Object + forwarder (forwarder)

WeakEventManager uses solution 0, and this solution uses the Weakeventhandler wrapper: Register a (object,forwarderdelegate) pair:

Eventsource.addhandler (This, eventsource.addhandler
(Me, sender, args) = ((Listenerobject) Me). OnEvent (sender, args));

Advantages:

Simple and effective.

Disadvantages:

An unconventional signature registers an event, and forwarding a lambda expression requires a type conversion (CAST).

Solution 3: Intelligent Weak event (smartweakevent)

The smartweakevent of the downloaded Code provides an event similar to a normal. NET event that preserves the weak references of event listeners, but is not subject to the "must-have-delegate reference" issue.

void Registerevent ()
{
Eventsource.event + = OnEvent;
}
void OnEvent (object sender, EventArgs e)
{

}

Event Definition:

Smartweakevent _event
= new smartweakevent ();

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 divided into one target (stored as a weak application) and MethodInfo, and the method is invoked with reflection when the event fires.

One possible problem here is that someone might attach an anonymous method as an event handler and capture a variable in an anonymous method.

int localvariable = 42;
Eventsource.event + = delegate {Console.WriteLine (localvariable);};

In this case, the delegate target object is closed-loop (closure) and can be garbage collected immediately, because no other object references it. However, Smartweakevent is able to detect this situation and throw an exception, so there will be no debugging difficulties, because the event handlers are logged off before we think we should log off.


typeof (Compilergeneratedattribute), false). Length! = 0)
throw new ArgumentException (...);

Advantages:

Seems to be a real weak event; there is little code overhead.

Disadvantages:

Reflection calls are slow.

Solution 4: Fast Intelligent Weak event (fastsmartweakevent)

Functionality and use are the same as smartweakevent, but significantly improve performance. Here are the test results for events with two registered delegates (one instance method and one static method):

Normal (strong) event ...  
Smart Weak event ...
Fast Smart Weak event ...

How does it work? Instead of using reflection to call the method, compile a forwarder method at run time (similar to the "Forward code" in the previous scenario) using System.Reflection.Emit.DynamicMethod.

Advantages:

Seems to be a real weak event; there is little code overhead.

Disadvantages:

-

Suggestions
    • Any object running on the WPF UI thread (for example, attaching an event to a custom control), using WeakEventManager;
    • If you want to provide a weak event, use fastsmartweakevent;
    • If you want to consume an event, use Weakeventhandler.

Translation PostScript

Recently, special attention has been paid. NET of delegates and events and related implementation techniques. When browsing CodeProject, I saw an article about weak events, which I read several times because of curiosity, and found that some of the ideas and methods were more profound and skillful, and clarified several misconceptions and ambiguities in the concept of events. This paper mainly discusses the implementation techniques of. NET 3.0 and future platforms. But the basic ideas (such as: WeakReference) can still be applied on. NET 2.0 and above. The content of the article is difficult to understand, whether the right or bad first translated, left to the practical application of the later learning and experience.

Weak events in C # (Weak events in C #)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.