Keine Typrückschlüsse mit der generischen Erweiterungsmethode

Ich habe folgende Methode:

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

Und diese class

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

Jetzt habe ich folgende Probleme:

  1. Diese Erweiterungsmethode wird bei allen Typen angezeigt, auch bei string .
  2. Ich kann keine new EventInvocatorParameters(EventABC).Until(e => false); schreiben new EventInvocatorParameters(EventABC).Until(e => false); Es sagt mir “Die Typargumente für Methode … können nicht aus der Verwendung abgeleitet werden.”

Kann ich generische Typparameter wie diese nicht verwenden? Wie würden Sie dieses Problem lösen?
Wichtiger Punkt: Ich brauche beide dieser generischen Parameter, weil ich denselben Typ zurückgeben muss, an dem diese Erweiterungsmethode aufgerufen wurde.


Breiteres Bild (nicht notwendig für die Beantwortung der Frage!):
Ich versuche, eine fließende Schnittstelle zum Aufrufen von Ereignissen zu erstellen. Die Basis ist diese statische class:

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

Um sicherzustellen, dass diese Methode nur mit vollständig konfigurierten Parametern aufgerufen werden kann, akzeptiert sie nur ein ConfiguredEventInvocatorParameters das von 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; } } 

Folgendes wären gültige Aufrufe:

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

Folgendes wäre ungültig:

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

Um dies zu ermöglichen, gibt es Erweiterungsmethoden namens With , die entweder einen EventHandler<TEventArgs oder einen TEventInvocatorParameters und einen ConfiguredEventInvocatorParameters . Alle Aufrufe, die dem With folgen, müssen nun auch den Typ ConfiguredEventInvocatorParameters , andernfalls würde das zweite Beispiel eines gültigen Aufrufs (mit dem Until am Ende) nicht funktionieren.
Wenn Sie irgendwelche Gedanken über die API im Allgemeinen haben, lassen Sie es mich bitte wissen. Ich möchte jedoch die folgenden drei Dinge vermeiden:

  • Nur zur Laufzeit auszufallen, wenn die Parameter nicht vollständig konfiguriert wurden
  • Erstellen einer inversen Syntax wie EventName.With(...).Until(...).Fire()
  • Verwenden Sie die berüchtigte Do Methode, um Dinge zu starten: Fire(EventName).With(...).Until(...).Do();

   

Generische Methoden-Typ-Inferenz macht bewusst keine Abzüge von den Constraints. Vielmehr werden Ableitungen von den Argumenten und den formalen Parametern gemacht , und dann werden die abgeleiteten Typargumente gegen die Beschränkungen geprüft.

Für eine detaillierte Diskussion einiger Design-Probleme rund um Constraints und Methodensignaturen, einschließlich mehrerer Dutzend Leute, die mir sagen, dass ich falsch liege, wenn ich denke, dass das existierende Design sinnvoll ist, siehe meinen Artikel zu diesem Thema:

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

Für alle Interessierten habe ich das ursprüngliche Problem (flüssige Aufruf-API) mit einer generischen classnhierarchie getriggers. Dies ist im Grunde Hightechrider’s Antwort auf Steroide.

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

So können Sie Code wie folgt schreiben:

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

Sie können dies jedoch nicht schreiben, da nicht alle notwendigen Informationen (eventArgs und Absender) zur Verfügung gestellt wurden:

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

Gibt es einen Grund, warum Sie eine Erweiterungsmethode verwenden müssen? Wenn Sie Until auf die class EventInvocatorParameters , können Sie die beiden genannten Probleme vermeiden:

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

Ich weiß, dass es sich um eine Ausrede handelt, aber haben Sie sich darüber Gedanken gemacht, stattdessen Rx zu verwenden, statt das, was Sie scheinbar zu tun versuchen, neu zu erfinden?