using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
namespace Rocky
{
internal delegate bool Forwarder(WeakReference weakRef, object sender, EventArgs e);
/// <summary>
/// The forwarder-generating code is in a separate class because it does not depend on type T.
/// </summary>
internal static class WeakEventForwarderProvider
{
private static MethodInfo getTarget;
private static Type[] forwarderParameters;
private static Hashtable forwarders;
static WeakEventForwarderProvider()
{
getTarget = typeof(WeakReference).GetMethod("get_Target");
forwarderParameters = new Type[] { typeof(WeakReference), typeof(object), typeof(EventArgs) };
forwarders = Hashtable.Synchronized(new Hashtable());
}
internal static Forwarder GetForwarder(MethodInfo method)
{
var fd = (Forwarder)forwarders[method];
if (fd != null)
{
return fd;
}
if (method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
{
throw new ArgumentException("Cannot create weak event to anonymous method with closure.");
}
ParameterInfo[] parameters = method.GetParameters();
DynamicMethod dm = new DynamicMethod("WeakEvent", typeof(bool), forwarderParameters, method.DeclaringType);
ILGenerator il = dm.GetILGenerator();
if (!method.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt, getTarget, null);
il.Emit(OpCodes.Dup);
Label label = il.DefineLabel();
il.Emit(OpCodes.Brtrue, label);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
il.MarkLabel(label);
// The castclass here is required for the generated code to be verifiable.
// We can leave it out because we know this cast will always succeed
// (the instance/method pair was taken from a delegate).
// Unverifiable code is fine because private reflection is only allowed under FullTrust
// anyways.
//il.Emit(OpCodes.Castclass, method.DeclaringType);
}
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
// This castclass here is required to prevent creating a hole in the .NET type system.
// See Program.TypeSafetyProblem in the 'SmartWeakEventBenchmark' to see the effect when
// this cast is not used.
// You can remove this cast if you trust add FastSmartWeakEvent.Raise callers to do
// the right thing, but the small performance increase (about 5%) usually isn't worth the risk.
il.Emit(OpCodes.Castclass, parameters[1].ParameterType);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
forwarders[method] = fd = (Forwarder)dm.CreateDelegate(typeof(Forwarder));
return fd;
}
internal static void OverForwarder(MethodInfo method)
{
forwarders.Remove(method);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Reflection;
using System.Reflection.Emit;
namespace Rocky
{
public sealed class WeakEvent<T> where T : class
{
#region NestedTypes
private struct EventEntry
{
public readonly Forwarder Forwarder;
public readonly MethodInfo TargetMethod;
public readonly WeakReference TargetReference;
public EventEntry(Forwarder forwarder, MethodInfo targetMethod, WeakReference targetReference)
{
this.Forwarder = forwarder;
this.TargetMethod = targetMethod;
this.TargetReference = targetReference;
}
}
#endregion
#region Static
static WeakEvent()
{
Type type = typeof(T);
if (!type.IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("T must be a delegate type");
}
MethodInfo invoke = type.GetMethod("Invoke");
if (invoke == null || invoke.GetParameters().Length != 2)
{
throw new ArgumentException("T must be a delegate type taking 2 parameters");
}
ParameterInfo senderParameter = invoke.GetParameters()[0];
if (senderParameter.ParameterType != typeof(object))
{
throw new ArgumentException("The first delegate parameter must be of type 'object'");
}
ParameterInfo argsParameter = invoke.GetParameters()[1];
if (!(typeof(EventArgs).IsAssignableFrom(argsParameter.ParameterType)))
{
throw new ArgumentException("The second delegate parameter must be derived from type 'EventArgs'");
}
if (invoke.ReturnType != typeof(void))
{
throw new ArgumentException("The delegate return type must be void.");
}
}
#endregion
private List<EventEntry> eventEntries;
private object SyncRoot
{
get { return eventEntries; }
}
public WeakEvent()
{
eventEntries = new List<EventEntry>();
}
private void RemoveEntry(int index, MethodInfo method)
{
lock (SyncRoot)
{
eventEntries.RemoveAt(index);
}
WeakEventForwarderProvider.OverForwarder(method);
}
private void RemoveDeadEntries()
{
lock (SyncRoot)
{
eventEntries.RemoveAll(entry =>
{
if (entry.TargetReference != null && !entry.TargetReference.IsAlive)
{
WeakEventForwarderProvider.OverForwarder(entry.TargetMethod);
return true;
}
return false;
});
}
}
public void Add(T e)
{
if (e == null)
{
return;
}
Delegate d = (Delegate)(object)e;
if (eventEntries.Count == eventEntries.Capacity)
{
RemoveDeadEntries();
}
MethodInfo targetMethod = d.Method;
object targetInstance = d.Target;
WeakReference target = targetInstance != null ? new WeakReference(targetInstance) : null;
lock (SyncRoot)
{
eventEntries.Add(new EventEntry(WeakEventForwarderProvider.GetForwarder(targetMethod), targetMethod, target));
}
}
public void Remove(T e)
{
if (e == null)
{
return;
}
Delegate d = (Delegate)(object)e;
object targetInstance = d.Target;
MethodInfo targetMethod = d.Method;
lock (SyncRoot)
{
for (int i = eventEntries.Count - 1; i >= 0; i--)
{
EventEntry entry = eventEntries[i];
if (entry.TargetReference != null)
{
object target = entry.TargetReference.Target;
if (target == null)
{
RemoveEntry(i, entry.TargetMethod);
}
else if (target == targetInstance && entry.TargetMethod == targetMethod)
{
RemoveEntry(i, entry.TargetMethod);
break;
}
}
else
{
if (targetInstance == null && entry.TargetMethod == targetMethod)
{
RemoveEntry(i, entry.TargetMethod);
break;
}
}
}
}
}
public bool Raise()
{
return Raise(this, EventArgs.Empty);
}
public bool Raise(object sender, EventArgs e)
{
bool needClear = false;
foreach (EventEntry entry in eventEntries)
{
needClear |= entry.Forwarder(entry.TargetReference, sender, e);
}
if (needClear)
{
RemoveDeadEntries();
}
return eventEntries.Count > 0;
}
}
}