Skip to content

Instantly share code, notes, and snippets.

@m93a
Created August 25, 2017 22:03
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 m93a/390b2d6a9fa3f166f3bf4d6698684dee to your computer and use it in GitHub Desktop.
Save m93a/390b2d6a9fa3f166f3bf4d6698684dee to your computer and use it in GitHub Desktop.
namespace HydraExtensions
{
/**
* <summary>
* This class contains extensions to event-related
* types, such as EventHandler and EventHandler&lt;T>.
* </summary>
**/
public static class EventExtensions
{
/**
* <summary>
* Given an EventHandler, returns a Task that will
* be completed when the event fires.
* </summary>
*
* <returns>
* A task which will complete when the event fires.
* The task.Result is a ValueTuple containing the
* two arguments that would be passed to an event
* handler (i.e. sender and args).
* </returns>
*
* <example>
* var form = new MyForm();
* await form.Once("Load"); //once the form loads
*
* var (sender, args) = await form.Button.Once("Click");
* Console.WriteLine("The button was just clicked");
* </example>
**/
public static async Task<EventResult> Once<T>(this T target, string eventName)
{
var tcs = new TaskCompletionSource<object[]>();
var eventInfo = target.GetType().GetEvent(eventName);
var delegateType = eventInfo.EventHandlerType;
DynamicMethod handler;
if (!dynamicHandlerCache.TryGetValue(delegateType, out handler))
{
Type returnType;
List<Type> parameterTypes;
ExtensionMethods.GetDelegateParameterAndReturnTypes(delegateType,
out parameterTypes, out returnType);
var invoke = delegateType.GetMethod("Invoke");
var parameters = (from p in invoke.GetParameters() select p.ParameterType).ToList();
if (returnType != typeof(void))
throw new NotSupportedException();
Type tcsType = tcs.GetType();
MethodInfo setResultMethodInfo = tcsType.GetMethod("SetResult");
// I'm going to create an instance-like method
// so, first argument must an instance itself
// i.e. TaskCompletionSourceHolder *this*
parameters.Insert(0, tcsType);
Type[] parameterTypesAr = parameters.ToArray();
handler = new DynamicMethod("unnamed",
returnType, parameterTypesAr, tcsType);
ILGenerator ilgen = handler.GetILGenerator();
// declare local variable of type object[]
LocalBuilder arr = ilgen.DeclareLocal(typeof(object[]));
// push array's size onto the stack
ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1);
// create an object array of the given size
ilgen.Emit(OpCodes.Newarr, typeof(object));
// and store it in the local variable
ilgen.Emit(OpCodes.Stloc, arr);
// iterate thru all arguments except the zero one (i.e. *this*)
// and store them to the array
for (int i = 1; i < parameterTypesAr.Length; i++)
{
// push the array onto the stack
ilgen.Emit(OpCodes.Ldloc, arr);
// push the argument's index onto the stack
ilgen.Emit(OpCodes.Ldc_I4, i - 1);
// push the argument onto the stack
ilgen.Emit(OpCodes.Ldarg, i);
// check if it is of a value type
// and perform boxing if necessary
if (parameterTypesAr[i].IsValueType)
ilgen.Emit(OpCodes.Box, parameterTypesAr[i]);
// store the value to the argument's array
ilgen.Emit(OpCodes.Stelem, typeof(object));
}
// load zero-argument (i.e. *this*) onto the stack
ilgen.Emit(OpCodes.Ldarg_0);
// load the array onto the stack
ilgen.Emit(OpCodes.Ldloc, arr);
// call this.SetResult(arr);
ilgen.Emit(OpCodes.Call, setResultMethodInfo);
// and return
ilgen.Emit(OpCodes.Ret);
dynamicHandlerCache.Add(delegateType, handler);
}
// Construct the delegate
Delegate deleg = handler.CreateDelegate(delegateType, tcs);
// target.event += deleg;
eventInfo.AddEventHandler(target, deleg);
// wait for it to fire
var args = await tcs.Task;
// target.event -= deleg;
eventInfo.RemoveEventHandler(target, deleg);
return new EventResult(args, delegateType);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment