WeakEventSource implementation

The Situation

Implementing custom controls with dependency properties binding to an IEnumarable object collection is not a developer’s daily task. Most of the time one would use the ItemsControl which already provides the ItemsSource property together with the ItemsTemplate property. But what when the functionality provided with the ItemsControl does not meet your requirement? Then you can implement your own dependency property for the source collection like the following:

#region DataPointsSource (DependencyProperty)

public IEnumerable DataPointsSource
{
    get { return (IEnumerable)GetValue(DataPointsSourceProperty); }
    set { SetValue(DataPointsSourceProperty, value); }
}
public static readonly DependencyProperty DataPointsSourceProperty =
    DependencyProperty.Register("DataPointsSource",
                        typeof(IEnumerable),
                        typeof(CustomControl),
                        new PropertyMetadata(new PropertyChangedCallback(OnDataPointsSourceChanged)));

private static void OnDataPointsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}  

#endregion

 

To fully support the Silverlight content model you should check whether the source collection implements INotifyCollectionChanged interface or not. If the source implements INotifyCollectionChanged the custom control code should response to this event and Add/Remove/Replace items accordingly. Attaching the necessary event as following can produce a serious memory leaking problem:

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // implements INotifyCollectionChanged?
    var sourceCollectionChanged = newValue as INotifyCollectionChanged;
    if (sourceCollectionChanged != null)
    {
        // attach the event
        sourceCollectionChanged.CollectionChanged += OnCollectionChanged;
    }
}

 

In a previous post I explained in detail why there can be a memory leak and how we prevent it in our SL controls. The solution is quite the same as Microsoft uses in the Silverlight Toolkit. Additionally David Anson wrote an excellent article about leaking controls and the solution they use in the Silverlight Toolkit.

So, writing code using the WeakEventListener pattern described in the blog posts above it still a tedious task. First you have to detach the previous listener and secondly you have to create a new weak-listener for the new event source. This result in these lines of code:

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // detach previous event source
    if (_weakEventListener != null)
    {
        //  detach WeakEventListener
        _weakEventListener.Detach();
        _weakEventListener = null;
    }

    // attach new WeakEventListener when new source implements INotifyCollectionChanged
    var sourceCollectionChanged = newValue as INotifyCollectionChanged;
    if ( sourceCollectionChanged != null)
    {
        // create WeakEventListener. Pass Listener and EventSource instance to it.
        var weakListener = new WeakEventListener<VisifireMvvmWrapperDemo, 
                                      INotifyCollectionChanged, 
                                      NotifyCollectionChangedEventArgs>(this, sourceCollectionChanged);
        // register handler
        sourceCollectionChanged.CollectionChanged += weakListener.OnEvent;
        // define event handler to handle the event.
        weakListener.OnEventAction = (instance, source, e) =>
        {
            // event handling code
            instance.OnCollectionChanged(instance, e);
        };
        // define action to detach the handler
        weakListener.OnDetachAction = (listener, source) =>
        {
            // unregister handler
            source.CollectionChanged -= listener.OnEvent;
        };
        // ensure there is no reference from the listener to the weakListener
        weakListener = null;
    }

}

Due to the fact that we use this pattern quite often for custom control development, I was looking for a solution which is easier to use.

Our solution

With an additional wrapper encapsulating the WeakEventListener our developers can now write just the following lines of code to get the same result:

private CollectionChangedWeakEventSource _sourceChanged;

public CustomControl()
{
    // setup wrapper for CollectionChanged
    _sourceChanged = new CollectionChangedWeakEventSource();
    _sourceChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(sourceChanged_CollectionChanged);
}

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // just set the event source to the wrapper
    _sourceChanged.SetEventSource(newValue);
}

void sourceChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{

}
 

In the control constructor (or when I use the wrapper the first time) I set up an instance of the CollectionChangedWeakEventSource and attach the CollectionChanged event. In the OnDataPointsSourceChanged handler I just set the source object for the wrapper. A previous source collection (oldValue) gets detached from the wrapper automatically when you call SetEventSource with the new value. The object passed to the method SetEventSource gets attached when the object implements INotifyCollectionChanged.

The class CollectionChangedWeakEventSource is quite easy to understand. It wraps just one event and uses the WeakEventListener class for the weak event pattern.

public class CollectionChangedWeakEventSource : WeakEventSourceBase<INotifyCollectionChanged>
{
  protected override WeakEventListenerBase CreateWeakEventListener(INotifyCollectionChanged eventObject)
  {
        // create a WeakEventListener
        var weakListener = new WeakEventListener<CollectionChangedWeakEventSource, 
                                                 INotifyCollectionChanged, 
                                                 NotifyCollectionChangedEventArgs>(this, eventObject);
        weakListener.OnDetachAction = (listener, source) =>
        {
            source.CollectionChanged -= listener.OnEvent;
        };
        weakListener.OnEventAction = (instance, source, e) =>
        {
            // fire event
            if (instance.CollectionChanged != null)
                instance.CollectionChanged(source, e);
        };
        eventObject.CollectionChanged += weakListener.OnEvent;

        return weakListener;
  }

  public event NotifyCollectionChangedEventHandler CollectionChanged;

}
 

The base class WeakEventSourceBase contains the general code to make the custom wrapper as simple as possible. As a result it is quite easy to implement an event wrapper for another event like the PropertyChanged event. Additionally I wrote a code snippet which is available here.

At the end of this post you can find the source code of WeakEventSourceBase.

Some explanation to the code:

  • WeakEventSourceBase contains a check to verify whether the implementation is correct or not. This check is executed only if a debugger is attached. Please note: I don’t use the Conditional(“DEBUG”) attribute to enable/disable this check, because we put the WeakEventSourceBase class into a common framework lib and this lib is RELEASE compiled.
  • Calling SetEventSource(null) is the same as calling Detach()
  • When setting a new event source, the previous source gets detached
  • When setting an event source which does not implement the event, it does not get attached, but the EventSource property is set to this instance and an previous event source gets detached.
  • The class WeakEventListenerBase is new due to the refactoring that was needed to get a non-generic declaration for the CreateWeakEventListener method return value.

Summary

The WeakEventSource implementation is a weak-event-pattern implementation based on the WeakEventListener. The class helps us to reduce writing tedious code in custom control development, reduces memory leaks which are hard to find and last but not least it is easier to review custom control written that way, because a Reviewer must not have full knowledge about the weak-event pattern.

In a blog post later this week I’m going to demonstrate a complete custom control implementation using this WeakEventSource implementation.

 

Source Code

Here you can find the full source code.

image

WeakEventSourceBase class:

/// <summary>
/// Base class to wrap a specific event with the WeakEventListener.
/// </summary>
/// <typeparam name="TSource">The type of the event source.</typeparam>
public abstract class WeakEventSourceBase<TSource>
     where TSource : class
{

    /// <summary>
    /// A weak reference to the event source
    /// </summary>
    private WeakReference _weakEventSource;

    /// <summary>
    ///  A weak reference to the WeakEventListener instance.
    /// </summary>
    private WeakReference _weakListener;

    /// <summary>
    /// Gets the event source instance which this listener is using.
    /// </summary>
    /// <remarks>
    /// The reference to the event source is weak.
    /// </remarks>
    public object EventSource
    {
        get
        {
            if (_weakEventSource == null)
                return null;

            return this._weakEventSource.Target;
        }
    }

    /// <summary>
    /// Set the event source for this instance. 
    /// When passing a new event source it replaces the event source the 
    /// listener is listen for an event. When passing null/nothing is detaches 
    /// the previous event source from this event listener. 
    /// </summary>
    /// <param name="eventSource">The event source instance.</param>
    public void SetEventSource(object eventSource)
    {
        // the listener can just listen for one event source. 
        // Detach the previous event source
        this.Detach();

        // keep weak-reference to the the event source
        this._weakEventSource = new WeakReference(eventSource);

        TSource eventObject = eventSource as TSource;
        if (eventObject != null)
        {
           var weakListener = CreateWeakEventListenerInternal(eventObject);

           if (weakListener == null)
              throw new InvalidOperationException("The method CreateWeakEventListener must return a value.");

           // store the weak-listener as weak reference (for Detach method only)
           _weakListener = new WeakReference(weakListener);
        }
    }

    /// <summary>
    /// Does some debug-time checks and creates the weak event listener.
    /// </summary>
    /// <param name="eventObject">The event source instance</param>
    /// <returns>Return the weak event listener instance</returns>
    private WeakEventListenerBase CreateWeakEventListenerInternal(TSource eventObject)
    {
        #region Debug time checks

        // do some implementation checks when a debugger is attached
        if (Debugger.IsAttached)
        {
            // search in each type separately unitl we reach the type WeakEventSourceBase 
            //(because Reflection can not return private members in FlattenHierarchy.
            Type type = this.GetType();
            while ((!type.IsGenericType || type.GetGenericTypeDefinition() 
!= typeof(WeakEventSourceBase<>)) && type != typeof(object)) { BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; // get fields expect fields marked with CompilerGeneratedAttribute or derived
// from Delegate (events are delegate fields)
var queryFields = from f in type.GetFields(bindingFlags) where f.GetCustomAttributes(typeof(CompilerGeneratedAttribute),true).Count()==0 && !f.FieldType.IsSubclassOf(typeof(Delegate)) select f.Name; // get properties var queryProperties = from f in type.GetProperties(bindingFlags) select f.Name; var query = queryFields.Union(queryProperties); // The EventWrapper is intended to be used as a weak-event-wrapper. One should not add // additional members to this class, because of the possibilty to store the // WeakEventListener reference to a member.
// Is this the case the memory leak can still occur.
// Therefore, if any field or property is implemented, throw an exception as warning. if (query.Count() > 0) { // note: MessageBox.Show blocks unit tests throw new InvalidOperationException(string.Format("You should not add any other “ +
“implementation than overriding methods in the class {0}, because of “ +
                             “possible memory you can get within your application.", type.Name));
                }

                // continue search in base type
                type = type.BaseType;
            }
        }

        #endregion

        // create weak event listener
        return CreateWeakEventListener(eventObject);
    }

    /// <summary>
    /// When overridden in a derived class, it creates the weak event 
/// listener for the given event source.
/// </summary> /// <param name="eventObject">The event source instance to listen for an event</param> /// <returns>Return the weak event listener instance</returns> protected abstract WeakEventListenerBase CreateWeakEventListener(TSource eventObject); /// <summary> /// Detaches the event from the event source. /// </summary> public void Detach() { if (_weakListener != null) { // do it the GC safe way, because an object could potentially be reclaimed // for garbage collection immediately after the IsAlive property returns true WeakEventListenerBase target = _weakListener.Target as WeakEventListenerBase; if (target != null) target.Detach(); } _weakEventSource = null; _weakListener = null; } }
About these ads
Tagged , , ,

8 thoughts on “WeakEventSource implementation

  1. chester ng says:

    Fantastic Work !

  2. Tony Farr says:

    Please could you tell me how you defined WeakEventListenerBase. Is it a reference that I need or is there some code missing?

    Thanks in advance.

    Tony

    • beatkiener says:

      Hi,

      I’ve just downloaded the source code from the blog and it is working fine. WeakEventListenerBase class is part of the zip file, isn’t it?

      Best regards,
      Beat

  3. Tony Farr says:

    Thanks Beatkiener. Initially, I copied the code off the screen but I will now download the zip file and try that.

    • Tony Farr says:

      I need to register a dependency property on a silverlight Page derived class but I’m getting an error about cannot set a read-only property. Does anyone know if it is possible to register a DP on a page instead of a UserControl, even though Page is derived from UC.

      Thanks,

      Tony.

  4. [...] posts have vexed poetic about the reasoning for needing weak events. You can read about them in many many places. Note – this post assumes you know what weak events are and how they [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: