Skip to content

Instantly share code, notes, and snippets.

@jmangelo
Created May 17, 2010 22:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmangelo/404312 to your computer and use it in GitHub Desktop.
Save jmangelo/404312 to your computer and use it in GitHub Desktop.
Helper Classes for creation of Weak Event Handlers
//
// Copyright 2010, João Angelo
//
// This file is part of Event Management Helpers.
//
// Event Management Helpers is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// Event Management Helpers is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Event Management Helpers. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Reflection;
namespace Helpers.Events
{
/// <summary>
/// Provides methods that allows a listener to subscribe to an event without
/// becoming referenced by the event source.
/// </summary>
public static class WeakEventHandlerWrapper
{
/// <summary>
/// Wraps the specified event handler in a delegate based call.
/// </summary>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler WrapInDelegateCall(EventHandler handler)
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
var genericType = typeof(DelegateBasedWeakEventHandler<,>);
var concreteType = genericType.MakeGenericType(typeof(EventArgs), handler.Target.GetType());
return (WeakEventHandler<EventArgs>)Activator.CreateInstance(concreteType, handler);
}
/// <summary>
/// Wraps the specified event handler in a delegate based call.
/// </summary>
/// <typeparam name="TListener">
/// The type of the listener that defines the <paramref name="handler"/>.
/// </typeparam>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <exception cref="ArgumentException">
/// The provided <paramref name="handler"/> is defined by a type different
/// than the one specified by <typeparamref name="TListener"/>.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler WrapInDelegateCall<TListener>(EventHandler handler)
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
if (handler.Target.GetType() != typeof(TListener))
throw new ArgumentException(
"The method is defined in a type different than TListener.",
"handler");
return new DelegateBasedWeakEventHandler<EventArgs, TListener>(handler);
}
/// <summary>
/// Wraps the specified event handler in a delegate based call.
/// </summary>
/// <typeparam name="TArgs">The type of the event data generated by the event.</typeparam>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler<TArgs> WrapInDelegateCall<TArgs>(EventHandler<TArgs> handler)
where TArgs : EventArgs
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
var genericType = typeof(DelegateBasedWeakEventHandler<,>);
var concreteType = genericType.MakeGenericType(typeof(TArgs), handler.Target.GetType());
return (WeakEventHandler<TArgs>)Activator.CreateInstance(concreteType, handler);
}
/// <summary>
/// Wraps the specified event handler in a delegate based call.
/// </summary>
/// <typeparam name="TArgs">The type of the event data generated by the event.</typeparam>
/// <typeparam name="TListener">
/// The type of the listener that defines the <paramref name="handler"/>.
/// </typeparam>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <exception cref="ArgumentException">
/// The provided <paramref name="handler"/> is defined by a type different
/// than the one specified by <typeparamref name="TListener"/>.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler<TArgs> WrapInDelegateCall<TArgs, TListener>(EventHandler<TArgs> handler)
where TArgs : EventArgs
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
if (handler.Target.GetType() != typeof(TListener))
throw new ArgumentException(
"The method is defined in a type different than TListener.",
"handler");
return new DelegateBasedWeakEventHandler<TArgs, TListener>(handler);
}
/// <summary>
/// Wraps the specified event handler in a reflection based call.
/// </summary>
/// <typeparam name="TArgs">The type of the event data generated by the event.</typeparam>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler<TArgs> WrapInReflectionCall<TArgs>(EventHandler<TArgs> handler)
where TArgs : EventArgs
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
return new ReflectionBasedWeakEventHandler<TArgs>(handler);
}
/// <summary>
/// Wraps the specified event handler in a reflection based call.
/// </summary>
/// <param name="handler">The handler.</param>
/// <exception cref="ArgumentNullException">
/// The parameter <paramref name="handler"/> is a null reference.
/// </exception>
/// <remarks>
/// If the provided <paramref name="handler"/> is a static method no wrapping is performed.
/// </remarks>
/// <returns>A wrapped event handler.</returns>
public static EventHandler WrapInReflectionCall(EventHandler handler)
{
if (handler == null)
throw new ArgumentNullException("handler");
if (handler.Target == null)
return handler;
return new ReflectionBasedWeakEventHandler<EventArgs>(handler);
}
}
internal abstract class WeakEventHandler<T> where T : EventArgs
{
public WeakEventHandler(Delegate handler)
{
this.WeakRef = new WeakReference(handler.Target, false);
this.Method = handler.Method;
}
protected WeakReference WeakRef { get; set; }
protected MethodInfo Method { get; set; }
public abstract void Invoke(object sender, EventArgs e);
public static implicit operator EventHandler(WeakEventHandler<T> weakHandler)
{
return weakHandler.Invoke;
}
public static implicit operator EventHandler<T>(WeakEventHandler<T> weakHandler)
{
return weakHandler.Invoke;
}
}
internal sealed class ReflectionBasedWeakEventHandler<T> : WeakEventHandler<T>
where T : EventArgs
{
public ReflectionBasedWeakEventHandler(Delegate handler)
: base(handler) { }
public override void Invoke(object sender, EventArgs e)
{
object target = this.WeakRef.Target;
if (target != null)
{
this.Method.Invoke(target, new object[] { sender, e });
}
}
}
internal delegate void OpenInstanceEventHandler<TTarget, TArgs>(
TTarget target,
object sender,
TArgs e) where TArgs : EventArgs;
internal sealed class DelegateBasedWeakEventHandler<TArgs, TListener> : WeakEventHandler<TArgs>
where TArgs : EventArgs
{
public DelegateBasedWeakEventHandler(Delegate handler)
: base(handler)
{
var weakHandler = Delegate.CreateDelegate(
typeof(OpenInstanceEventHandler<TListener, TArgs>),
null,
handler.Method,
true);
this.Handler = (OpenInstanceEventHandler<TListener, TArgs>)weakHandler;
}
private OpenInstanceEventHandler<TListener, TArgs> Handler { get; set; }
public override void Invoke(object sender, EventArgs e)
{
object target = this.WeakRef.Target;
if (target != null)
{
this.Handler((TListener)target, sender, (TArgs)e);
}
}
}
}
using System;
using NUnit.Framework;
namespace Helpers.Events.UnitTests
{
using Wrapper = WeakEventHandlerWrapper;
[TestFixture]
public class WeakEventHandlerWrapperTest
{
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInReflectionCall_T001()
{
Assert.Throws<ArgumentNullException>(() => Wrapper.WrapInReflectionCall(null));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInReflectionCall_T002()
{
EventHandler expected = EventListener.HandleStaticRaised;
var actual = Wrapper.WrapInReflectionCall(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInReflectionCall_T003()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInReflectionCall(test.Listener.HandleRaised);
test.Raiser.Raise();
Assert.IsTrue(test.Result.RaisedCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInReflectionCall_T004()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInReflectionCall(test.Listener.HandleRaised);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
test.Raiser.Raise();
Assert.IsFalse(test.Result.RaisedCalled);
}
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInReflectionCall_TArgs_T001()
{
EventHandler<RaisedIdEventArgs> handler = null;
Assert.Throws<ArgumentNullException>(() => Wrapper.WrapInReflectionCall(handler));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInReflectionCall_TArgs_T002()
{
EventHandler<RaisedIdEventArgs> expected = EventListener.HandleStaticRaisedId;
var actual = Wrapper.WrapInReflectionCall(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInReflectionCall_TArgs_T003()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInReflectionCall<RaisedIdEventArgs>(
test.Listener.HandleRaisedId);
test.Raiser.RaiseId(1);
Assert.IsTrue(test.Result.RaisedIdCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInReflectionCall_TArgs_T004()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInReflectionCall<RaisedIdEventArgs>(
test.Listener.HandleRaisedId);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
test.Raiser.RaiseId(1);
Assert.IsFalse(test.Result.RaisedIdCalled);
}
[Test]
[Description("SHOULD NOT affect event data passed to the handler.")]
public void WrapInReflectionCall_TArgs_T005()
{
var test = new EventTestContext();
const int Id = 1;
EventHandler<RaisedIdEventArgs> handler = (sender, e) => Assert.AreEqual(Id, e.Id);
test.Raiser.RaisedId += Wrapper.WrapInReflectionCall(handler);
test.Raiser.RaiseId(Id);
}
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInDelegateCall_T001()
{
Assert.Throws<ArgumentNullException>(() => Wrapper.WrapInDelegateCall(null));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInDelegateCall_T002()
{
EventHandler expected = EventListener.HandleStaticRaised;
var actual = Wrapper.WrapInDelegateCall(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInDelegateCall_T003()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInDelegateCall(test.Listener.HandleRaised);
test.Raiser.Raise();
Assert.IsTrue(test.Result.RaisedCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInDelegateCall_T004()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInDelegateCall(test.Listener.HandleRaised);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
test.Raiser.Raise();
Assert.IsFalse(test.Result.RaisedCalled);
}
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInDelegateCall_TListener_T001()
{
EventHandler handler = null;
Assert.Throws<ArgumentNullException>(() => Wrapper.WrapInDelegateCall<EventListener>(handler));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInDelegateCall_TListener_T002()
{
EventHandler expected = EventListener.HandleStaticRaised;
var actual = Wrapper.WrapInDelegateCall<EventListener>(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInDelegateCall_TListener_T003()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInDelegateCall<EventListener>(test.Listener.HandleRaised);
test.Raiser.Raise();
Assert.IsTrue(test.Result.RaisedCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInDelegateCall_TListener_T004()
{
var test = new EventTestContext();
test.Raiser.Raised += Wrapper.WrapInDelegateCall<EventListener>(
test.Listener.HandleRaised);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(test.Result.RaisedCalled);
}
[Test]
[Description("An exception SHOULD be thrown on type mismatch between compile time listener type and runtime type of handler target.")]
public void WrapInDelegateCall_TListener_T005()
{
var test = new EventTestContext();
Assert.Throws<ArgumentException>(
() => Wrapper.WrapInDelegateCall<EventRaiser>(test.Listener.HandleRaised));
}
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInDelegateCall_TArgs_T001()
{
EventHandler<RaisedIdEventArgs> handler = null;
Assert.Throws<ArgumentNullException>(() => Wrapper.WrapInDelegateCall(handler));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInDelegateCall_TArgs_T002()
{
EventHandler<RaisedIdEventArgs> expected = EventListener.HandleStaticRaisedId;
var actual = Wrapper.WrapInDelegateCall(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInDelegateCall_TArgs_T003()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall<RaisedIdEventArgs>(
test.Listener.HandleRaisedId);
test.Raiser.RaiseId(1);
Assert.IsTrue(test.Result.RaisedIdCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInDelegateCall_TArgs_T004()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall<RaisedIdEventArgs>(
test.Listener.HandleRaisedId);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
test.Raiser.RaiseId(1);
Assert.IsFalse(test.Result.RaisedIdCalled);
}
[Test]
[Description("SHOULD NOT affect event data passed to the handler.")]
public void WrapInDelegateCall_TArgs_T005()
{
var test = new EventTestContext();
const int Id = 1;
EventHandler<RaisedIdEventArgs> handler = (sender, e) => Assert.AreEqual(Id, e.Id);
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall(handler);
test.Raiser.RaiseId(Id);
}
[Test]
[Description("An exception SHOULD be thrown if the provided handler is a null reference.")]
public void WrapInDelegateCall_TArgs_TListener_T001()
{
EventHandler<RaisedIdEventArgs> handler = null;
Assert.Throws<ArgumentNullException>(
() => Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventListener>(handler));
}
[Test]
[Description("SHOULD return exact same handler if provided with a static method handler.")]
public void WrapInDelegateCall_TArgs_TListener_T002()
{
EventHandler<RaisedIdEventArgs> expected = EventListener.HandleStaticRaisedId;
var actual = Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventListener>(expected);
Assert.AreEqual(expected, actual);
}
[Test]
[Description("Wrapped handler SHOULD be invoked if listener instance is not disposed.")]
public void WrapInDelegateCall_TArgs_TListener_T003()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventListener>(
test.Listener.HandleRaisedId);
test.Raiser.RaiseId(1);
Assert.IsTrue(test.Result.RaisedIdCalled);
}
[Test]
[Description("Wrapped handler SHOULD NOT be invoked if listener instance is disposed.")]
public void WrapInDelegateCall_TArgs_TListener_T004()
{
var test = new EventTestContext();
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventListener>(
test.Listener.HandleRaisedId);
test.Listener = null;
GC.Collect();
GC.WaitForPendingFinalizers();
test.Raiser.RaiseId(1);
Assert.IsFalse(test.Result.RaisedIdCalled);
}
[Test]
[Description("An exception SHOULD be thrown on type mismatch between compile time listener type and runtime type of handler target.")]
public void WrapInDelegateCall_TArgs_TListener_T005()
{
var test = new EventTestContext();
Assert.Throws<ArgumentException>(
() => Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventRaiser>(
test.Listener.HandleRaisedId));
}
[Test]
[Description("SHOULD NOT affect event data passed to the handler.")]
public void WrapInDelegateCall_TArgs_TListener_T006()
{
var test = new EventTestContext();
const int Id = 1;
EventHandler<RaisedIdEventArgs> handler = (sender, e) => Assert.AreEqual(Id, e.Id);
test.Raiser.RaisedId += Wrapper.WrapInDelegateCall<RaisedIdEventArgs, EventListener>(handler);
test.Raiser.RaiseId(Id);
}
}
internal class EventTestContext
{
public EventTestContext()
{
this.Raiser = new EventRaiser();
this.Result = new EventListenerResult();
this.Listener = new EventListener(this.Result);
}
public EventRaiser Raiser { get; set; }
public EventListener Listener { get; set; }
public EventListenerResult Result { get; set; }
}
internal class EventListenerResult
{
public bool RaisedCalled { get; set; }
public bool RaisedIdCalled { get; set; }
}
internal class EventListener
{
public EventListener(EventListenerResult result)
{
this.Result = result;
}
public EventListenerResult Result { get; set; }
public void HandleRaised(object sender, EventArgs e)
{
this.Result.RaisedCalled = true;
}
public void HandleRaisedId(object sender, RaisedIdEventArgs e)
{
this.Result.RaisedIdCalled = true;
}
public static void HandleStaticRaised(object sender, EventArgs e) { }
public static void HandleStaticRaisedId(object sender, RaisedIdEventArgs e) { }
}
internal class RaisedIdEventArgs : EventArgs
{
public int Id { get; set; }
}
internal class EventRaiser
{
public event EventHandler Raised;
public event EventHandler<RaisedIdEventArgs> RaisedId;
public void Raise() { this.OnRaised(EventArgs.Empty); }
public void RaiseId(int id) { this.OnRaisedId(new RaisedIdEventArgs { Id = id }); }
protected virtual void OnRaised(EventArgs e)
{
var handler = this.Raised;
if (handler != null)
handler(this, e);
}
protected virtual void OnRaisedId(RaisedIdEventArgs e)
{
var handler = this.RaisedId;
if (handler != null)
handler(this, e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment