Reputation: 10781
In this response, I came up with the following helper method that could be reused by different Task
creators to convert events into task completion sources.
// Helper method
static Task<T> TaskFromEventHelper<T>(object target, string eventName, Func<TaskCompletionSource<T>, object> resultSetterFactory) {
var tcs = new TaskCompletionSource<T>();
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var d = Delegate.CreateDelegate(delegateType, resultSetterFactory(tcs), "Invoke");
addMethod.Invoke(target, new object[] {d});
return tcs.Task;
}
// Empty events (Action style)
static Task TaskFromEvent(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<object>, object>)(tcs => (Action)(() => tcs.SetResult(null))));
}
// One-value events (Action<T> style)
static Task<T> TaskFromEvent<T>(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<T>, object>)(tcs => (Action<T>)(tcs.SetResult)));
}
// Two-value events (Action<T1, T2> or EventHandler style)
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<Tuple<T1, T2>>, object>)(tcs => (Action<T1, T2>)((t1, t2) => tcs.SetResult(Tuple.Create(t1, t2)))));
}
In each of the three examples I gave that use the helper method, there's a tcs.SetResult
component, which makes me think there's a way to move that to the helper method too, which might perhaps simplify the signatures, so that perhaps the helper method would just have to accept a Func<?, T>
, where that Func
would take the output of the event
and convert it to whatever tcs.SetResult
takes.
i.e., I'm thinking there must be a way to create a helper so I can write it as
// Empty events (Action style)
static Task TaskFromEvent(object target, string eventName) {
return TaskFromEventHelper<object>(target, eventName, new Func<object>(() => null));
}
// One-value events (Action<T> style)
static Task<T> TaskFromEvent<T>(object target, string eventName) {
return TaskFromEventHelper<T>(target, eventName, new Func<T, T>(t => t));
}
// Two-value events (Action<T1, T2> or EventHandler style)
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) {
return TaskFromEventHelper<Tuple<T1, T2>>(target, eventName, new Func<T1, T2, Tuple<T1, T2>>(Tuple.Create));
}
, but that's why I don't know the ?
in Func<?, T>
above. This one for example needs ?
to be two parameters. Could it be passed in as object
somehow? I have a feeling it could be possible, but if so it needs some real reflection magic.
Upvotes: 3
Views: 285
Reputation: 34407
You can use Expression
:
static Task<T> TaskFromEventHelper<T>(object target, string eventName, Delegate resultSetter)
{
var tcs = new TaskCompletionSource<T>();
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var methodInfo = delegateType.GetMethod("Invoke");
var parameters = methodInfo.GetParameters()
.Select(a => Expression.Parameter(a.ParameterType))
.ToArray();
// building method, which argument count and
// their types are not known at compile time
var exp = // (%arguments%) => tcs.SetResult(resultSetter.Invoke(%arguments%))
Expression.Lambda(
delegateType,
Expression.Call(
Expression.Constant(tcs),
tcs.GetType().GetMethod("SetResult"),
Expression.Call(
Expression.Constant(resultSetter),
resultSetter.GetType().GetMethod("Invoke"),
parameters)),
parameters);
addMethod.Invoke(target, new object[] { exp.Compile() });
return tcs.Task;
}
Upvotes: 3