C # Generics erlaubt keine Delegate Type Constraints

79

Ist es möglich, eine Klasse in C # so zu definieren, dass

class GenericCollection<T> : SomeBaseCollection<T> where T : Delegate

Ich konnte diese letzte Nacht in .NET 3.5 für mein ganzes Leben nicht erreichen. Ich habe es versucht

delegate, Delegate, Action<T> and Func<T, T>

Es scheint mir, dass dies in irgendeiner Weise zulässig sein sollte. Ich versuche meine eigene EventQueue zu implementieren.

Am Ende habe ich nur dies getan [wohlgemerkt primitive Annäherung].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

Aber dann verliere ich die Fähigkeit, dieselbe Definition für verschiedene Arten von Funktionen wiederzuverwenden.

Gedanken?

Nicholas Mancuso
quelle

Antworten:

66

Eine Reihe von Klassen sind als allgemeine Einschränkungen nicht verfügbar - Enum ist eine andere.

Für Teilnehmer, ist die nächst man bekommen kann „: Klasse“, vielleicht mithilfe von Reflektion überprüfen (zum Beispiel in dem statischen Konstruktor) , dass die T ist ein Delegierter:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}
Marc Gravell
quelle
8
+1 für: 1) Verwenden des statischen Konstruktors und 2) Einschließen einer detaillierten Nachricht aufgrund seltsamer Debugging-Bedingungen bei der Typinitialisierung.
Sam Harwell
6
@MarcGravell: Das Auslösen einer Ausnahme in einem statischen Initialisierer verstößt nicht CA1065: Do not raise exceptions in unexpected locations... Ich war immer unter der Annahme, dass Sie eine benutzerdefinierte Code-Analyseregel verwenden sollten, um ungültige Verwendungen Ihrer Klasse zu finden, die normalerweise zur Laufzeit nicht verfügbar sind.
Myermian
3
Ab C # 7.3 (veröffentlicht im Mai 2018) darf es so eingeschränkt where T : Delegatewerden (und jemand hat unten eine neue Antwort dazu gepostet).
Jeppe Stig Nielsen
16

Ja , es ist möglich , in C # 7.3, Constraints Familie gehört erhöht Enum, Delegateund unmanagedTypen. Sie können diesen Code problemlos schreiben:

void M<D, E, T>(D d, E e, T* t) where D : Delegate where E : Enum where T : unmanaged
    {

    }

Aus Dokumenten :

Ab C # 7.3 können Sie mithilfe der nicht verwalteten Einschränkung angeben, dass der Typparameter ein nicht nullbarer nicht verwalteter Typ sein muss. Mit der nicht verwalteten Einschränkung können Sie wiederverwendbare Routinen schreiben, um mit Typen zu arbeiten, die als Speicherblöcke bearbeitet werden können

Nützliche Links:

Die Zukunft von C # aus Microsoft Build 2018

Was ist neu in C # 7.3?

mshwf
quelle
Ja, dies ist in C # 7.3 (seit Mai 2018) möglich. Versionshinweise finden Sie hier .
Jeppe Stig Nielsen
13

Bearbeiten: In diesen Artikeln werden einige vorgeschlagene Problemumgehungen vorgeschlagen:

http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html

http://jacobcarpenters.blogspot.com/2006_11_01_archive.html


Aus der C # 2.0-Spezifikation können wir lesen (20.7, Constraints):

Eine Einschränkung vom Typ Klasse muss die folgenden Regeln erfüllen:

  • Der Typ muss ein Klassentyp sein.
  • Der Typ darf nicht versiegelt werden.
  • Der Typ darf nicht einer der folgenden Typen sein: System.Array, System.Delegate, System.Enum oder System.ValueType .
  • Der Typ darf kein Objekt sein. Da alle Typen vom Objekt abgeleitet sind, hätte eine solche Einschränkung keine Auswirkung, wenn sie zulässig wäre.
  • Höchstens eine Einschränkung für einen bestimmten Typparameter kann ein Klassentyp sein.

Und sicher spuckt VS2008 einen Fehler aus:

error CS0702: Constraint cannot be special class 'System.Delegate'

Informationen und Informationen zu diesem Thema finden Sie hier .

Jorge Ferreira
quelle
10

Wenn Sie bereit sind, eine Kompilierungszeitabhängigkeit von einem IL Weaver zu übernehmen, können Sie dies mit Fody tun .

Verwenden dieses Add-Ins zu Fody https://github.com/Fody/ExtraConstraints

Ihr Code kann so aussehen

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
} 

Und dazu zusammengestellt werden

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
Simon
quelle
Defekter Link. Hast du eine aktuelle?
Justin Morgan
3

Der Delegat unterstützt bereits die Verkettung. Entspricht das nicht Ihren Bedürfnissen?

public class EventQueueTests
{
    public void Test1()
    {
        Action myAction = () => Console.WriteLine("foo");
        myAction += () => Console.WriteLine("bar");

        myAction();
        //foo
        //bar
    }

    public void Test2()
    {
        Action<int> myAction = x => Console.WriteLine("foo {0}", x);
        myAction += x => Console.WriteLine("bar {0}", x);
        myAction(3);
        //foo 3
        //bar 3
    }

    public void Test3()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        myFunc += x => { Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 3
        //4
    }

    public void Test4()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        Func<int, int> myNextFunc = x => { x = myFunc(x);  Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myNextFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 5
        //6
    }

}
Amy B.
quelle
Das ist nicht wirklich die Funktionalität, nach der ich suche ... Ich habe versucht, eine Typbeschränkung für meine generische Klasse festzulegen ...
Nicholas Mancuso
3

Ich stieß auf eine Situation, in der ich mich Delegateintern mit einem Problem befassen musste, aber ich wollte eine generische Einschränkung. Insbesondere wollte ich einen Ereignishandler mithilfe von Reflection hinzufügen, aber ich wollte ein generisches Argument für den Delegaten verwenden. Der folgende Code funktioniert NICHT, da "Handler" eine Typvariable ist und der Compiler nicht Handlerin Folgendes umwandelt Delegate:

public void AddHandler<Handler>(Control c, string eventName, Handler d) {
  c.GetType().GetEvent(eventName).AddEventHandler(c, (Delegate) d);
}

Sie können jedoch eine Funktion übergeben, die die Konvertierung für Sie durchführt. convertnimmt ein HandlerArgument und gibt ein Delegate:

public void AddHandler<Handler>(Control c, string eventName, 
                  Func<Delegate, Handler> convert, Handler d) {
      c.GetType().GetEvent(eventName).AddEventHandler(c, convert(d));
}

Jetzt ist der Compiler glücklich. Das Aufrufen der Methode ist einfach. Beispiel: Anhängen an das KeyPressEreignis in einem Windows Forms-Steuerelement:

AddHandler<KeyEventHandler>(someControl, 
           "KeyPress", 
           (h) => (KeyEventHandler) h,
           SomeControl_KeyPress);

Wo SomeControl_KeyPressist das Ereignisziel? Der Schlüssel ist der Konverter Lambda - er funktioniert nicht, überzeugt aber den Compiler, den Sie ihm als gültigen Delegaten gegeben haben.

(Beginnen Sie mit 280Z28) @Justin: Warum nicht?

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
  c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate); 
} 

(Ende 280Z28)

Justin Bailey
quelle
1
@Justin: Ich habe Ihre Antwort bearbeitet, um meinen Kommentar am Ende zu platzieren, da er einen Codeblock enthält.
Sam Harwell
2

Wie oben erwähnt, können Sie Delegates und Enum nicht als allgemeine Einschränkung verwenden. System.ObjectundSystem.ValueType kann auch nicht als generische Einschränkung verwendet werden.

Die Problemumgehung kann sein, wenn Sie in Ihrer IL einen entsprechenden Aufruf erstellen. Es wird gut funktionieren.

Hier ist ein gutes Beispiel von Jon Skeet.

http://code.google.com/p/unconstrained-melody/

Ich habe meine Referenzen aus Jon Skeets Buch C # in Depth , 3. Auflage, entnommen .

maxspan
quelle
1

Laut MSDN

Compilerfehler CS0702

Einschränkung kann keine spezielle Klassen-ID sein. Die folgenden Typen dürfen nicht als Einschränkungen verwendet werden:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
quelle
Warum wiederholst du die Frage hier? Sie erzählen uns nichts Neues.
Elmue