Skip to content

Instantly share code, notes, and snippets.

@maxpert maxpert/Readme.md
Last active Dec 11, 2015

Embed
What would you like to do?
[Portable Class Library] WeakLambda implementation (can be used to make replacement of WeakEventManager in PCL environments).

Nuts and bolts for a Portable Class Library WeakEventManager

I spent my whole day (as a hobby) figuring out how can I implement equivalent of WeakEventManager. After a whole lot of digging around I concluded that I need a basic WeakLambda or WeakAction implementation. This implementation in-turn can be used by anything ranging from bulding WeakEvents, to WeakEventManager.

You are free to copy, rewrite, distribute code! May you never do bad and share the love!

Zohaib Sibte Hassan

namespace EventTester
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Weirdo;
using System.Diagnostics;
using System.Threading.Tasks;
[TestClass]
public class UnitTest
{
internal class Foo
{
string name = "Chicken";
public void Eat(string s)
{
Debug.WriteLine(this.name, s);
}
public static void EatStatic(string s)
{
Debug.WriteLine("Static eat!", s);
}
~Foo()
{
Debug.WriteLine("Destructing");
}
}
private WeakLambda<string> weakLambda;
[TestInitialize]
public void TestInit()
{
}
[TestMethod]
public void TestMethod1()
{
this.RegisterFoo();
GC.Collect(2);
GC.WaitForFullGCComplete();
var tsk = Task.Run(() =>
{
this.weakLambda.Invoke("Task");
});
Task.WaitAll(tsk);
}
private void RegisterFoo()
{
var f = new Foo();
this.weakLambda = new WeakLambda<string>(Foo.EatStatic);
this.weakLambda.Invoke("Register!");
}
}
}
namespace Weirdo
{
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
/// <summary>
/// Weak lambda implementation that let's you invoke Action taking parameter T
/// without keeping strong reference to the instance of type T
/// </summary>
/// <typeparam name="T">Callback parameter to action</typeparam>
public class WeakLambda<T>
{
/// <summary>
/// Concurrent cache dictionary to prevent runtime overhead
/// </summary>
private static readonly ConcurrentDictionary<MethodInfo, Action<object, T>> OpenHandleCache;
/// <summary>
/// The weak reference to the reference target
/// </summary>
private WeakReference weakTarget;
/// <summary>
/// Method info of passed action
/// </summary>
private MethodInfo methodInfo;
/// <summary>
/// Compiled LINQ lambda expression
/// </summary>
private Action<object, T> compiledExpressionMethod;
/// <summary>
/// Compiles given method to LINQ lambda expression invoking the method
/// </summary>
/// <param name="method">The method information</param>
private static Action<object, T> CompileActionExpression(MethodInfo method)
{
var target = Expression.Parameter(typeof(object), "target");
var e = Expression.Parameter(typeof(T), "e");
if (method.IsStatic)
{
var staticCall = Expression.Call(method, e);
return Expression.Lambda<Action<object, T>>(staticCall, target, e)
.Compile();
}
else
{
var thisCastExp = Expression.Convert(target, method.DeclaringType);
var boundCallExp = Expression.Call(thisCastExp, method, e);
return Expression.Lambda<Action<object, T>>(boundCallExp, target, e)
.Compile();
}
}
/// <summary>
/// Compiles and caches expression from method info
/// </summary>
/// <param name="info">The method info</param>
private static Action<object, T> GetExpression(MethodInfo info)
{
return WeakLambda<T>.OpenHandleCache.GetOrAdd(info, WeakLambda<T>.CompileActionExpression);
}
/// <summary>
/// Static constructor
/// </summary>
static WeakLambda()
{
WeakLambda<T>.OpenHandleCache = new ConcurrentDictionary<MethodInfo, Action<object, T>>();
}
/// <summary>
/// Creates instance of WeakLambda
/// </summary>
/// <param name="action">The callback method to be invoked via WeakLambda</param>
public WeakLambda(Action<T> action)
{
// Set weak target to null in case of static methods
this.weakTarget = (action.Target != null) ? new WeakReference(action.Target) : null;
this.methodInfo = action.GetMethodInfo();
this.compiledExpressionMethod = WeakLambda<T>.GetExpression(this.methodInfo);
}
/// <summary>
/// Invoke the stored action using args if object reference still exists.
/// </summary>
/// <param name="args">Arguments to pass to action</param>
/// <returns>true if method was invoked; false otherwise</returns>
public bool Invoke(T args)
{
var self = this.weakTarget?.Target;
// Incase of non-static self must not be null
if (this.weakTarget != null && self == null)
{
return false;
}
this.compiledExpressionMethod(self, args);
return true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.