Método de uso geral FromEvent
Usando o novo modelo async / await é bastante simples gerar umTask
que é concluído quando um evento é disparado; você só precisa seguir esse padrão:
public class MyClass
{
public event Action OnCompletion;
}
public static Task FromEvent(MyClass obj)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
obj.OnCompletion += () =>
{
tcs.SetResult(null);
};
return tcs.Task;
}
Isto então permite:
await FromEvent(new MyClass());
O problema é que você precisa criar um novoFromEvent
método para cada evento em todas as classes que você gostaria deawait
em. Isso pode ficar muito grande muito rápido, e é basicamente apenas código clichê.
Idealmente eu gostaria de poder fazer algo assim:
await FromEvent(new MyClass().OnCompletion);
Então eu poderia reutilizar o mesmoFromEvent
método para qualquer evento em qualquer instância. Eu passei algum tempo tentando criar um método desses, e há vários obstáculos. Para o código acima, irá gerar o seguinte erro:
O evento 'Namespace.MyClass.OnCompletion' só pode aparecer no lado esquerdo de + = ou - =
Tanto quanto eu posso dizer, nunca haverá uma maneira de passar o evento assim através do código.
Então, a próxima melhor coisa parecia estar tentando passar o nome do evento como uma string:
await FromEvent(new MyClass(), "OnCompletion");
Não é tão ideal; você não obtém intellisense e receberia um erro de tempo de execução se o evento não existisse para esse tipo, mas ainda assim poderia ser mais útil do que toneladas de métodos FromEvent.
Então é fácil usar reflexão eGetEvent(eventName)
para obter oEventInfo
objeto. O próximo problema é que o delegado desse evento não é conhecido (e precisa ser capaz de variar) em tempo de execução. Isso torna a adição de um manipulador de eventos difícil, porque precisamos criar dinamicamente um método em tempo de execução, correspondendo a uma determinada assinatura (mas ignorando todos os parâmetros) que acessa umTaskCompletionSource
que já temos e define seu resultado.
Felizmente eu encontreiesse link que contém instruções sobre como fazer [quase] exatamente isso viaReflection.Emit
. Agora o problema é que precisamos emitir IL, e não tenho idéia de como acessar otcs
exemplo que eu tenho.
Abaixo está o progresso que fiz para terminar isso:
public static Task FromEvent<T>(this T obj, string eventName)
{
var tcs = new TaskCompletionSource<object>();
var eventInfo = obj.GetType().GetEvent(eventName);
Type eventDelegate = eventInfo.EventHandlerType;
Type[] parameterTypes = GetDelegateParameterTypes(eventDelegate);
DynamicMethod handler = new DynamicMethod("unnamed", null, parameterTypes);
ILGenerator ilgen = handler.GetILGenerator();
//TODO ilgen.Emit calls go here
Delegate dEmitted = handler.CreateDelegate(eventDelegate);
eventInfo.AddEventHandler(obj, dEmitted);
return tcs.Task;
}
Qual IL eu poderia emitir que me permitiria definir o resultado doTaskCompletionSource
? Ou, alternativamente, existe outra abordagem para criar um método que retorna uma tarefa para qualquer evento arbitrário de um tipo arbitrário?