Created
August 25, 2017 22:03
-
-
Save m93a/390b2d6a9fa3f166f3bf4d6698684dee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace HydraExtensions | |
{ | |
/** | |
* <summary> | |
* This class contains extensions to event-related | |
* types, such as EventHandler and EventHandler<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