通用FromEvent 方法
問題:
建立在事件發生時完成的一個在事件發生時完成的Taskletion fires 需要為每個類別中的每個事件使用自訂FromEvent 方法。這變得麻煩且重複。
所需的解決方案:
通用的 FromEvent 方法,可以處理任何實例上的任何事件。
困難點:
解決方案:
以下程式碼提供了克服這些困難的通用 FromEvent方法:
internal class TaskCompletionSourceHolder { private readonly TaskCompletionSource<object[]> m_tcs; internal object Target { get; set; } internal EventInfo EventInfo { get; set; } internal Delegate Delegate { get; set; } internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc) { m_tcs = tsc; } private void SetResult(params object[] args) { // this method will be called from emitted IL // so we can set result here, unsubscribe from the event // or do whatever we want. // object[] args will contain arguments // passed to the event handler m_tcs.SetResult(args); EventInfo.RemoveEventHandler(Target, Delegate); } } public static class ExtensionMethods { private static Dictionary<Type, DynamicMethod> s_emittedHandlers = new Dictionary<Type, DynamicMethod>(); private static void GetDelegateParameterAndReturnTypes(Type delegateType, out List<Type> parameterTypes, out Type returnType) { if (delegateType.BaseType != typeof(MulticastDelegate)) throw new ArgumentException("delegateType is not a delegate"); MethodInfo invoke = delegateType.GetMethod("Invoke"); if (invoke == null) throw new ArgumentException("delegateType is not a delegate."); ParameterInfo[] parameters = invoke.GetParameters(); parameterTypes = new List<Type>(parameters.Length); for (int i = 0; i < parameters.Length; i++) parameterTypes.Add(parameters[i].ParameterType); returnType = invoke.ReturnType; } public static Task<object[]> FromEvent<T>(this T obj, string eventName) { var tcs = new TaskCompletionSource<object[]>(); var tcsh = new TaskCompletionSourceHolder(tcs); EventInfo eventInfo = obj.GetType().GetEvent(eventName); Type eventDelegateType = eventInfo.EventHandlerType; DynamicMethod handler; if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler)) { Type returnType; List<Type> parameterTypes; GetDelegateParameterAndReturnTypes(eventDelegateType, out parameterTypes, out returnType); if (returnType != typeof(void)) throw new NotSupportedException(); Type tcshType = tcsh.GetType(); MethodInfo setResultMethodInfo = tcshType.GetMethod( "SetResult", BindingFlags.NonPublic | BindingFlags.Instance); // I'm going to create an instance-like method // so, first argument must an instance itself // i.e. TaskCompletionSourceHolder *this* parameterTypes.Insert(0, tcshType); Type[] parameterTypesAr = parameterTypes.ToArray(); handler = new DynamicMethod("unnamed", returnType, parameterTypesAr, tcshType); 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); s_emittedHandlers.Add(eventDelegateType, handler); } Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh); tcsh.Target = obj; tcsh.EventInfo = eventInfo; tcsh.Delegate = dEmitted; eventInfo.AddEventHandler(obj, dEmitted); return tcs.Task; } }
此解決方案的優點:
以上是如何在 C# 中為 TaskCompletionSource 建立通用 FromEvent 方法?的詳細內容。更多資訊請關注PHP中文網其他相關文章!