Skip to content

Commit

Permalink
Add a WeakEventSource
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-englert committed Feb 12, 2018
1 parent a9d45b2 commit ae80f96
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 2 deletions.
10 changes: 10 additions & 0 deletions TomsToolbox.Core/TomsToolbox.Core.ExternalAnnotations.xml
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,16 @@
<attribute ctor="M:JetBrains.Annotations.NotNullAttribute.#ctor" />
</parameter>
</member>
<member name="M:TomsToolbox.Core.WeakEventSource`1.Subscribe(System.EventHandler{`0})">
<parameter name="handler">
<attribute ctor="M:JetBrains.Annotations.NotNullAttribute.#ctor" />
</parameter>
</member>
<member name="M:TomsToolbox.Core.WeakEventSource`1.Unsubscribe(System.EventHandler{`0})">
<parameter name="handler">
<attribute ctor="M:JetBrains.Annotations.NotNullAttribute.#ctor" />
</parameter>
</member>
<member name="M:TomsToolbox.Core.WeakReference`1.#ctor(`0)">
<parameter name="target">
<attribute ctor="M:JetBrains.Annotations.CanBeNullAttribute.#ctor" />
Expand Down
152 changes: 152 additions & 0 deletions TomsToolbox.Core/WeakEventSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
namespace TomsToolbox.Core
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

using JetBrains.Annotations;

/// <summary>
/// A simple weak event source implementation; useful for static events where you don't want to keep a reference to the event sink.
/// </summary>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <example>
/// Use like this:
/// <code language="C#">
/// <![CDATA[
/// class SampleSource
/// {
/// private readonly WeakEventSource<EventArgs> _source = new WeakEventSource<EventArgs>();
///
/// public event EventHandler AnyAction
/// {
/// add => _sentEventSource.Subscribe(value);
/// remove => _sentEventSource.Unsubscribe(value);
/// }
///
/// private void OnAnyAction()
/// {
/// _source.Raise(this, EventArgs.Empty);
/// }
/// }
///
/// class SampleSink
/// {
/// public SampleSink()
/// {
/// var source = new SampleSource();
/// source.AnyAction += Source_AnyAction;
/// }
///
/// private void Source_AnyAction(object sender, EventArgs e)
/// {
/// ... do something
/// }
/// }
/// ]]>
/// </code>
/// </example>
public class WeakEventSource<TEventArgs>
where TEventArgs : EventArgs
{
[NotNull, ItemNotNull]
// ReSharper disable once AssignNullToNotNullAttribute
private readonly List<WeakDelegate> _handlers = new List<WeakDelegate>();

/// <summary>
/// Raises the event with the specified sender and argument.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see paramref="TEventArgs"/> instance containing the event data.</param>
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
public void Raise(object sender, TEventArgs e)
{
lock (_handlers)
{
var garbageCollectedHandlers = _handlers
.Where(h => !h.Invoke(sender, e))
.ToArray();

_handlers.RemoveRange(garbageCollectedHandlers);
}
}

/// <summary>
/// Subscribes the specified handler for the event.
/// </summary>
/// <param name="handler">The handler.</param>
public void Subscribe([NotNull] EventHandler<TEventArgs> handler)
{
// ReSharper disable once AssignNullToNotNullAttribute
var weakHandlers = handler
.GetInvocationList()
// ReSharper disable once AssignNullToNotNullAttribute
.Select(d => new WeakDelegate(d))
.ToArray();

lock (_handlers)
{
_handlers.AddRange(weakHandlers);
}
}

/// <summary>
/// Unsubscribes the specified handler from the event.
/// </summary>
/// <param name="handler">The handler.</param>
public void Unsubscribe([NotNull] EventHandler<TEventArgs> handler)
{
lock (_handlers)
{
while (true)
{
var index = _handlers.FindIndex(h1 => h1.Matches(handler));
if (index < 0)
return;

_handlers.RemoveAt(index);
}
}

}

private class WeakDelegate
{
[CanBeNull]
private readonly WeakReference _weakTarget;
[NotNull]
private readonly MethodInfo _method;

public WeakDelegate([NotNull] Delegate handler)
{
_weakTarget = handler.Target != null ? new WeakReference(handler.Target) : null;
// ReSharper disable once AssignNullToNotNullAttribute
_method = handler.GetMethodInfo();
}

public bool Invoke(object sender, TEventArgs e)
{
object target = null;

if (_weakTarget != null)
{
target = _weakTarget.Target;
if (target == null)
return false;
}

_method.Invoke(target, new[] { sender, e });

return true;
}

public bool Matches([NotNull] EventHandler<TEventArgs> handler)
{
return ReferenceEquals(handler.Target, _weakTarget?.Target)
&& Equals(handler.GetMethodInfo(), _method);
}
}
}
}
1 change: 1 addition & 0 deletions TomsToolbox.Wpf/Interactivity/MapPanBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ protected override void OnAttached()
_storyboard.Completed += Storyboard_Completed;
}

/// <inheritdoc />
protected override void OnAssociatedObjectLoaded()
{
base.OnAssociatedObjectLoaded();
Expand Down
1 change: 1 addition & 0 deletions TomsToolbox.Wpf/Interactivity/MapZoomBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ protected override void OnAttached()
_storyboard.Completed += Storyboard_Completed;
}

/// <inheritdoc />
protected override void OnAssociatedObjectLoaded()
{
base.OnAssociatedObjectLoaded();
Expand Down
2 changes: 1 addition & 1 deletion TomsToolbox.Wpf/Interactivity/TimerTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/// </summary>
public sealed class TimerTrigger : System.Windows.Interactivity.TriggerBase<FrameworkElement>
{
[CanBeNull]
[CanBeNull]
private DispatcherTimer _timer;

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion TomsToolbox.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeProtected_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BETWEEN_USING_GROUPS/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
Expand Down

0 comments on commit ae80f96

Please sign in to comment.