Propósito general del método FromEvent

Usar el nuevo modelo async / await es bastante sencillo generar unTask que se completa cuando se dispara un evento; solo necesitas seguir este patrón:

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;
}

Esto entonces permite:

await FromEvent(new MyClass());

El problema es que necesitas crear un nuevoFromEvent Método para cada evento en cada clase que le gustaríaawait en. Eso podría llegar a ser realmente grande, muy rápido, y de todas formas es solo un código repetitivo.

Idealmente, me gustaría poder hacer algo como esto:

await FromEvent(new MyClass().OnCompletion);

Entonces podría reutilizar el mismoFromEvent Método para cualquier evento en cualquier caso. He pasado algún tiempo tratando de crear un método así, y hay una serie de inconvenientes. Para el código anterior se generará el siguiente error:

El evento 'Namespace.MyClass.OnCompletion' solo puede aparecer en el lado izquierdo de + = o - =

Por lo que puedo decir, nunca habrá una manera de pasar el evento de esta manera a través del código.

Entonces, la siguiente mejor cosa parecía ser tratar de pasar el nombre del evento como una cadena:

await FromEvent(new MyClass(), "OnCompletion");

No es tan ideal; no obtienes la inteligencia y obtendrías un error de tiempo de ejecución si el evento no existiera para ese tipo, pero aún podría ser más útil que toneladas de métodos FromEvent.

Así que es bastante fácil usar la reflexión yGetEvent(eventName) para obtener elEventInfo objeto. El siguiente problema es que el delegado de ese evento no se conoce (y debe poder variar) en el tiempo de ejecución. Eso dificulta la adición de un controlador de eventos, ya que necesitamos crear dinámicamente un método en tiempo de ejecución, haciendo coincidir una firma dada (pero ignorando todos los parámetros) que accede a unTaskCompletionSource Que ya tenemos y establece su resultado.

Afortunadamente me encontréeste enlace que contiene instrucciones sobre cómo hacer [casi] exactamente eso a través deReflection.Emit. Ahora el problema es que necesitamos emitir IL, y no tengo idea de cómo acceder a latcs instancia que tengo.

A continuación se muestra el progreso que he hecho para terminar esto:

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;
}

¿Qué IL podría emitir que me permitiría establecer el resultado de laTaskCompletionSource? O, alternativamente, ¿hay otro enfoque para crear un método que devuelva una Tarea para cualquier evento arbitrario de un tipo arbitrario?

Respuestas a la pregunta(3)

Su respuesta a la pregunta