No hay inferencia de tipo con el método de extensión genérico

Tengo el siguiente método:

public static TEventInvocatorParameters Until (this TEventInvocatorParameters p, Func breakCond) where TEventInvocatorParameters : EventInvocatorParameters where TEventArgs : EventArgs { p.BreakCondition = breakCond; return p; } 

Y esta clase

 public class EventInvocatorParameters where T : EventArgs { public Func BreakCondition { get; set; } // Other properties used below omitted for brevity. } 

Ahora, tengo los siguientes problemas:

  1. Este método de extensión se muestra en todos los tipos, incluso en string .
  2. No puedo escribir new EventInvocatorParameters(EventABC).Until(e => false); Me dice “Los argumentos de tipo para el método … no se pueden deducir del uso”.

¿Puedo usar parámetros de tipo genérico como este? ¿Cómo resolverías este problema?
Punto importante: necesito ambos parámetros generics, porque necesito devolver el mismo tipo al que se llamó este método de extensión.


Imagen más amplia (¡no es necesaria para responder la pregunta!):
Intento crear una interfaz fluida para invocar eventos. La base es esta clase estática:

 public static class Fire { public static void Event( ConfiguredEventInvocatorParameters parameters) where TEventArgs : EventArgs { if (parameters.EventHandler == null) { return; } var sender = parameters.Sender; var eventArgs = parameters.EventArgs; var breakCondition = parameters.BreakCondition; foreach (EventHandler @delegate in parameters.EventHandler.GetInvocationList()) { try { @delegate(sender, eventArgs); if (breakCondition(eventArgs)) { break; } } catch (Exception e) { var exceptionHandler = parameters.ExceptionHandler; if (!exceptionHandler(e)) { throw; } } } } } 

Para asegurarse de que este método solo pueda EventInvocatorParameters con parámetros totalmente configurados, solo acepta un ConfiguredEventInvocatorParameters que deriva de EventInvocatorParameters :

 public class ConfiguredEventInvocatorParameters : EventInvocatorParameters where T : EventArgs { public ConfiguredEventInvocatorParameters( EventInvocatorParameters parameters, object sender, T eventArgs) : base(parameters) { EventArgs = eventArgs; Sender = sender; } public T EventArgs { get; private set; } public object Sender { get; private set; } } 

Las siguientes serían llamadas válidas:

 Fire.Event(EventName.With(sender, eventArgs)); Fire.Event(EventName.With(sender, eventArgs).Until(e => e.Cancel)); Fire.Event(EventName.Until(e => e.Cancel).With(sender, eventArgs)); 

Lo siguiente sería inválido:

 // no sender or eventArgs have been specified, ie missing call to With(...) Fire.Event(EventName.Until(e => e.Cancel)); 

Para que esto funcione, existen métodos de extensión llamados With , que aceptan un EventHandler<TEventArgs o un TEventInvocatorParameters y devuelven un ConfiguredEventInvocatorParameters . Todas las llamadas que siguen al With ahora también necesitan devolver el tipo ConfiguredEventInvocatorParameters , de lo contrario, el segundo ejemplo de una llamada válida (con el Until al final) no funcionaría.
Si tiene alguna idea sobre la API en general, hágamelo saber. Sin embargo, quiero evitar las siguientes tres cosas:

  • Falla solo en el tiempo de ejecución si los parámetros no se han configurado completamente
  • Creando una syntax inversa como EventName.With(...).Until(...).Fire()
  • Utilice el infame método Do para comenzar cosas: Fire(EventName).With(...).Until(...).Do();

La inferencia de tipo de método genérico deliberadamente no hace ninguna deducción de las restricciones. Por el contrario, las deducciones se realizan a partir de los argumentos y los parámetros formales , y luego los argumentos del tipo deducido se comparan con las restricciones.

Para ver un análisis detallado 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:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

Para cualquiera que esté interesado, 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 sobre los esteroides.

 public abstract class EventInvocatorParametersBase  where TEventArgs : EventArgs where TEventInvocatorParameters : EventInvocatorParametersBase { protected EventInvocatorParametersBase( EventHandler eventHandler, Func exceptionHandler, Func breakCondition) { EventHandler = eventHandler; ExceptionHandler = exceptionHandler; BreakCondition = breakCondition; } protected EventInvocatorParametersBase( EventHandler eventHandler) : this(eventHandler, e => false, e => false) { } public Func BreakCondition { get; set; } public EventHandler EventHandler { get; set; } public Func ExceptionHandler { get; set; } public TEventInvocatorParameters Until( Func breakCondition) { BreakCondition = breakCondition; return (TEventInvocatorParameters)this; } public TEventInvocatorParameters WithExceptionHandler( Func exceptionHandler) { ExceptionHandler = exceptionHandler; return (TEventInvocatorParameters)this; } public ConfiguredEventInvocatorParameters With( object sender, TEventArgs eventArgs) { return new ConfiguredEventInvocatorParameters( EventHandler, ExceptionHandler, BreakCondition, sender, eventArgs); } } public class EventInvocatorParameters : EventInvocatorParametersBase, T> where T : EventArgs { public EventInvocatorParameters(EventHandler eventHandler) : base(eventHandler) { } } public class ConfiguredEventInvocatorParameters : EventInvocatorParametersBase, T> where T : EventArgs { public ConfiguredEventInvocatorParameters( EventHandler eventHandler, Func exceptionHandler, Func breakCondition, object sender, T eventArgs) : base(eventHandler, exceptionHandler, breakCondition) { EventArgs = eventArgs; Sender = sender; } public ConfiguredEventInvocatorParameters(EventHandler 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 Until( this EventHandler eventHandler, Func breakCondition) where TEventArgs : EventArgs { return new EventInvocatorParameters(eventHandler). Until(breakCondition); } public static EventInvocatorParameters WithExceptionHandler( this EventHandler eventHandler, Func exceptionHandler) where TEventArgs : EventArgs { return new EventInvocatorParameters(eventHandler). WithExceptionHandler(exceptionHandler); } public static ConfiguredEventInvocatorParameters With( this EventHandler eventHandler, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs { return new ConfiguredEventInvocatorParameters( eventHandler, sender, eventArgs); } } 

Esto le permite escribir un 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); 

¿Hay alguna razón por la que necesitas usar un método de extensión? Si coloca Until en la EventInvocatorParameters puede evitar los dos problemas mencionados:

 public class EventInvocatorParameters where T : EventArgs { public Func BreakCondition { get; set; } // Other properties used below omitted for brevity. public EventInvocatorParameters Until (Func breakCond) { this.BreakCondition = breakCond; return this; } } 

Estoy seguro de que es una especie de escape, pero ¿ha considerado utilizar Rx en lugar de volver a inventar lo que parece que está intentando hacer?