80 votes

Les génériques C # n'autorisent pas les contraintes de type de délégué

Est-il possible de définir une classe en C#, tels que

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

Je ne pouvais pas pour la vie de m'accomplir cette dernière nuit .NET 3.5. J'ai essayé d'utiliser

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

Il me semble que cela devrait être permis, d'une certaine façon. Je suis en train de mettre en place mes propres EventQueue.

J'ai juste le faire [primitive rapprochement que vous avez l'esprit].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

Mais je perds la possibilité de réutiliser la même définition pour les différents types de fonctions.

Pensées?

66voto

Marc Gravell Points 482669

Un certain nombre de classes ne sont pas disponibles en tant que contraintes génériques - Enum étant une autre.

Pour les délégués, le plus proche que vous puissiez obtenir est ": class", en utilisant peut-être une réflexion pour vérifier (par exemple, dans le constructeur statique) que le T est un délégué:

     static GenericCollection()
    {
        if (!typeof(T).IsSubclassOf(typeof(Delegate)))
        {
            throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
        }
    }
 

13voto

smink Points 39640

Edit: Certains ont proposé des solutions de rechange sont proposées dans ces articles:

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

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


Du C# 2.0 des spécifications , nous pouvons lire (20.7, Contraintes):

Une classe de type de contrainte doivent satisfaire aux règles suivantes:

  • Le type doit être un type de classe.
  • Le type ne doit pas être scellé.
  • Le type ne doivent pas être l'un des types suivants: Système d'.Tableau, Système.Délégué Système.Enum, ou d'un Système.ValueType.
  • Le type ne doit pas être l'objet. Parce que tous les types de dériver de l'objet, telle contrainte serait sans effet si elle était autorisée.
  • Au plus une contrainte pour un paramètre de type peut être un type de classe.

Et bien sûr, VS2008 crache une erreur:

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

Pour info et enquête sur cette question, lire ici.

10voto

Simon Points 11945

Si vous êtes prêt à prendre une dépendance de compilation sur un IL Weaver, vous pouvez le faire avec Fody .

Utilisation de ce complément pour Fody https://github.com/Fody/ExtraConstraints

Votre code peut ressembler à ceci

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

Et être compilé à cela

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

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
 

3voto

David B Points 53123

Le délégué soutient déjà le chaînage. Cela ne répond-il pas à vos besoins?

 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
    }

}
 

3voto

Justin Bailey Points 449

Je suis tombé sur une situation où j'avais besoin de traiter avec un Delegate en interne, mais je voulais une contrainte générique. Plus précisément, je voulais ajouter un gestionnaire d'événement à l'aide de la réflexion, mais je voulais utiliser un argument générique pour le délégué. Le code ci-dessous ne fonctionne PAS, depuis le "Gestionnaire" est un type de variable, et que le compilateur n'a pas coulé Handler de Delegate:

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

Toutefois, vous pouvez passer d'une fonction qui fait la conversion pour vous. convert prend un Handler argument et renvoie un Delegate:

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

Maintenant que le compilateur est heureux. L'appel de la méthode est facile. Par exemple, en joignant à l' KeyPress événement sur un contrôle Windows Forms:

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

SomeControl_KeyPress est la cible de l'événement. La clé est le convertisseur de lambda - il n'a pas de travail, mais il convainc le compilateur vous lui avez donné un valide délégué.

(Commencer 280Z28) @Justin: Pourquoi ne pas l'utiliser?

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

(Fin 280Z28)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X