Sin inferencia de tipo con método de extensión genérico

Sin inferencia de tipo con método de extensión genérico

ACTUALIZACIÓN de noviembre de 2020 :La respuesta original a continuación fue escrita en 2011; las reglas para la inferencia de tipo de método genérico, la resolución de sobrecarga y cómo se realiza la "validación final" de los métodos han tenido cambios pequeños pero significativos en versiones recientes de C#; esta respuesta, y el enlace a un artículo archivado en mi blog original de MSDN al respecto, podrían no ser precisos. Además, Microsoft eliminó los comentarios del artículo original por motivos legales; hubo una gran cantidad de contexto y discusión en esos comentarios. Espero en algún momento tener tiempo para revisar este artículo para aclarar (1) las reglas de hoy, (2) cómo han cambiado y (3) cómo las ideas discutidas en esos comentarios eliminados influyeron en esas decisiones, pero eso es mucho. de trabajo y es posible que no lo haga durante algún tiempo. Recuerde, no he estado en el equipo de diseño del lenguaje C# desde noviembre de 2012.

La inferencia de tipo de método genérico deliberadamente no hacer deducciones de las restricciones. Más bien, las deducciones se hacen a partir de los argumentos y los parámetros formales , y luego los argumentos de tipo deducido se comparan con las restricciones.

Para obtener una discusión detallada de algunos de los problemas de diseño relacionados con las restricciones y las firmas de métodos, incluidas varias docenas de personas que me dicen que me equivoco al pensar que el diseño existente es sensato, consulte mi artículo sobre el tema:

https://docs.microsoft.com/en-gb/archive/blogs/ericlippert/las restricciones-no-son-parte-de-la-firma


Para cualquier persona interesada, por ahora, resolví el problema original (API de invocación de eventos fluidos) con una jerarquía de clases genérica. Esta es básicamente la respuesta de Hightechrider con esteroides.

public abstract class EventInvocatorParametersBase
    <TEventInvocatorParameters, TEventArgs>
    where TEventArgs : EventArgs
    where TEventInvocatorParameters :
        EventInvocatorParametersBase<TEventInvocatorParameters, TEventArgs>

{
    protected EventInvocatorParametersBase(
        EventHandler<TEventArgs> eventHandler,
        Func<Exception, bool> exceptionHandler,
        Func<TEventArgs, bool> breakCondition)
    {
        EventHandler = eventHandler;
        ExceptionHandler = exceptionHandler;
        BreakCondition = breakCondition;
    }

    protected EventInvocatorParametersBase(
        EventHandler<TEventArgs> eventHandler)
        : this(eventHandler, e => false, e => false)
    {
    }

    public Func<TEventArgs, bool> BreakCondition { get; set; }
    public EventHandler<TEventArgs> EventHandler { get; set; }
    public Func<Exception, bool> ExceptionHandler { get; set; }

    public TEventInvocatorParameters Until(
        Func<TEventArgs, bool> breakCondition)
    {
        BreakCondition = breakCondition;
        return (TEventInvocatorParameters)this;
    }

    public TEventInvocatorParameters WithExceptionHandler(
        Func<Exception, bool> exceptionHandler)
    {
        ExceptionHandler = exceptionHandler;
        return (TEventInvocatorParameters)this;
    }

    public ConfiguredEventInvocatorParameters<TEventArgs> With(
        object sender, 
        TEventArgs eventArgs)
    {
        return new ConfiguredEventInvocatorParameters<TEventArgs>(
            EventHandler, ExceptionHandler, BreakCondition,
            sender, eventArgs);
    }
}

public class EventInvocatorParameters<T> :
    EventInvocatorParametersBase<EventInvocatorParameters<T>, T>
    where T : EventArgs
{
    public EventInvocatorParameters(EventHandler<T> eventHandler)
        : base(eventHandler)
    {
    }
}

public class ConfiguredEventInvocatorParameters<T> :
    EventInvocatorParametersBase<ConfiguredEventInvocatorParameters<T>, T>
    where T : EventArgs
{
    public ConfiguredEventInvocatorParameters(
        EventHandler<T> eventHandler,
        Func<Exception, bool> exceptionHandler,
        Func<T, bool> breakCondition, object sender,
        T eventArgs)
        : base(eventHandler, exceptionHandler, breakCondition)
    {
        EventArgs = eventArgs;
        Sender = sender;
    }

    public ConfiguredEventInvocatorParameters(EventHandler<T> eventHandler,
                                              object sender,
                                              T eventArgs)
        : this(eventHandler, e => false, e => false, sender, eventArgs)
    {
    }

    public T EventArgs { get; private set; }
    public object Sender { get; private set; }
}

public static class EventExtensions
{
    public static EventInvocatorParameters<TEventArgs> Until<TEventArgs>(
        this EventHandler<TEventArgs> eventHandler,
        Func<TEventArgs, bool> breakCondition)
        where TEventArgs : EventArgs
    {
        return new EventInvocatorParameters<TEventArgs>(eventHandler).
            Until(breakCondition);
    }

    public static EventInvocatorParameters<TEventArgs> 
        WithExceptionHandler<TEventArgs>(
            this EventHandler<TEventArgs> eventHandler,
            Func<Exception, bool> exceptionHandler)
        where TEventArgs : EventArgs
    {
        return
            new EventInvocatorParameters<TEventArgs>(eventHandler).
                WithExceptionHandler(exceptionHandler);
    }

    public static ConfiguredEventInvocatorParameters<TEventArgs>
        With<TEventArgs>(
            this EventHandler<TEventArgs> eventHandler, object sender,
            TEventArgs eventArgs)
        where TEventArgs : EventArgs
    {
        return new ConfiguredEventInvocatorParameters<TEventArgs>(
            eventHandler, sender, eventArgs);
    }
}

Esto le permite escribir código como este:

Fire.Event(EventName.WithExceptionHandler(e => false)
                    .Until(e => false).With(this, EventArgs.Empty));
Fire.Event(EventName.With(this, EventArgs.Empty));
Fire.Event(EventName.WithExceptionHandler(e => false)
                    .With(this, EventArgs.Empty).Until(e => false));
Fire.Event(EventName.With(this, EventArgs.Empty)
                    .WithExceptionHandler(e => false).Until(e => false));

Pero no le permite escribir esto, porque no se ha proporcionado toda la información necesaria (eventArgs y remitente):

Fire.Event(EventName.Until(e => false));
Fire.Event(EventName);