web-dev-qa-db-fra.com

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

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

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

Je ne pourrais pas pour la vie de moi accomplir cette dernière nuit dans .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 manière ou d'une autre. J'essaie d'implémenter mon propre EventQueue.

J'ai fini par faire cette [approximation primitive, pensez-vous].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

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

Pensées?

76
Nicholas Mancuso

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 pouvez obtenir est ": class", peut-être en utilisant la réflexion pour vérifier (par exemple, dans le constructeur statique) que le T is un délégué:

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

Modifier: Certaines solutions de contournement proposées 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


D'après la spécification C # 2. nous pouvons lire (20.7, Contraintes):

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

  • Le type doit être un type de classe.
  • Le type ne doit pas être scellé.
  • Le type ne doit pas être l'un des types suivants: System.Array, System.Delegate, System.Enum ou System.ValueType .
  • Le type ne doit pas être objet. Étant donné que tous les types dérivent d'un objet, une telle contrainte n'aurait aucun effet si elle était autorisée.
  • Au plus, une contrainte pour un paramètre de type donné peut être un type de classe.

Et bien sûr, VS2008 crache une erreur:

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

Pour plus d'informations et une enquête sur ce problème, lisez ici .

13
Jorge Ferreira

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

Utiliser cet addin 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é pour cela

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

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

Oui, c'est possible en C # 7.3, la famille des contraintes a été augmentée pour inclure les types Enum, Delegate et unmanaged. Vous pouvez écrire ce code sans problème:

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

    }

Liens utiles:

L'avenir de C # , de Microsoft Build 2018

Quoi de neuf dans C # 7.3?

9
mshwf

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
    }

}
3
Amy B

Je suis tombé sur une situation où je devais gérer un Delegate en interne mais je voulais une contrainte générique. Plus précisément, je voulais ajouter un gestionnaire d'événements à 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, car "Handler" est une variable de type et le compilateur ne transtypera pas Handler en Delegate:

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

Cependant, vous pouvez transmettre une fonction qui effectue la conversion pour vous. convert prend un argument Handler 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, le compilateur est content. Appeler la méthode est simple. Par exemple, l'attachement à l'événement KeyPress 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 lambda - il ne fonctionne pas, mais il convainc le compilateur que vous lui avez donné un délégué valide.

(Begin 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)

3
Justin Bailey

Comme mentionné ci-dessus, vous ne pouvez pas avoir Délégués et Enum comme contrainte générique. System.Object et System.ValueType ne peut pas non plus être utilisé comme contrainte générique.

Le travail peut être si vous construisez un appel approprié dans votre IL. Cela fonctionnera bien.

Voici un bon exemple de Jon Skeet.

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

J'ai pris mes références dans le livre de Jon Skeet C # in Depth , 3rd edition.

2
maxspan

Selon MSDN

Erreur du compilateur CS0702

La contrainte ne peut pas être une classe spéciale 'identificateur' Les types suivants ne peuvent pas être utilisés comme contraintes:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
1
Rahul Nikate