9
votes

Se désabonner de délégué passa par mot-clé refer à la méthode d'abonnement?

J'ai la classe suivante: xxx

J'ai recherché un moment et il apparaît qu'un argument passé avec le mot clé refanche ne peut pas être stocké. Essayer d'ajouter l'argument source à une liste ou de l'attribuer à une variable de champ ne lui permet pas de conserver une référence à la référence initiale du déléguée; Donc, mes questions sont:

  • Y a-t-il un moyen de vous désabonner de toutes les sources sans passer à nouveau leurs références?
  • Sinon, comment la classe peut-elle être modifiée afin de le soutenir, mais de maintenir toujours l'abonnement en adoptant un délégué par une méthode?
  • est-il possible de le réaliser sans utiliser de réflexion?
  • est-il possible de le réaliser sans envelopper le délégué / événement dans une classe, puis passer la classe comme un paramètre pour l'abonnement?

    merci.

    EDIT: Il apparaît que sans utiliser d'emballage ou de réflexion, il n'y a pas de solution au problème donné. Mon intention était de rendre la classe autant portée possible que possible, sans avoir à envelopper des délégués dans des classes d'assistance. Merci à tous pour les contributions.


3 commentaires

Utiliserait un événement avec un Ajouter / Supprimer Le gestionnaire fonctionne mieux pour vous?


Il serait plus facile de mettre en œuvre une classe wrapper puis, car j'ai beaucoup d'événements et écrire des implémentations personnalisées pour eux rendrait le code assez illisible. Mon but est de rendre la classe autant compatible que possible.


Mon exemple n'utilise pas d'emballage, même si cela nécessite un peu plus de travail supplémentaire (créant / passant deux délégués au lieu d'un délégué ref). J'ai remarqué que le framework rx est Quelque chose similiaire < / a>. Cela me rappelait cette question et ma réponse. Vaut probablement la peine de regarder pour votre solution.


6 Réponses :


2
votes

EDIT STRY>: OK, c'était une mauvaise idée, alors retour aux bases:

Je recommande de créer une classe d'emballage sur une action: p>

private ActionWrapper localSource;

public void Subscribe(ActionWrapper source)
{
    source.Action += Broadcast;
    localSource = source;        
}

public void Dispose()
{
    localSource.Action -= Broadcast;
}


10 commentaires

@Mike Christensen: Oui, je suis probablement mal compris. Édité maintenant.


Cela ne fonctionnera pas. Une fois stocké dans un champ ou dans une liste, la référence au délégué réel n'est pas stockée. Je viens de le tester pour être sûr et, en fait, cela ne se désabonne pas avec succès.


Oui, je pense que l'affiche disait que cela n'a pas fonctionné (les arguments passés par REF ne peuvent pas être stockés?) - Peut-être que l'affiche peut peut-être clarifier ce qu'ils veulent dire.


Y a-t-il une raison pour laquelle vous devez passer par Réf?


Si le délégué n'est pas adopté par REF, la méthode souscrite ne sera pas ajoutée à la liste d'invocation du délégué donné. Je suppose que cela crée une nouvelle instance du délégué qui n'est pas corrigée dans le gestionnaire d'origine.


@ user1098567: J'ai fait une édition que je pense est la solution.


Merci. Cela travaillerait certainement gentiment, mais je me demandais, comme mentionné dans le quatrième point de la question (que j'ai édité dans un instant plus tard, je m'excuse si c'était invisible), s'il était possible de le faire sans emballer l'événement / le délégué dans une classe d'assistance.


Je me demande si vous pouvez faire quelque chose de complètement funky, par exemple, écrivez une conversion implicite entre ActionWrapper et délégué ..


@ user1098567: J'ai bien peur que sans wrapper ce ne soit impossible. Si vous n'utilisez pas Ref, les objets ne sont passés que par la valeur, vous ne pouvez donc pas les modifier. Si vous utilisez Réf, ils ne peuvent pas être stockés, comme vous l'avez dit. Donc, le seul moyen de faire une "Réfertitude qui peut aussi être stockée" est d'utiliser une enveloppe.


@ user1098567, rappelez-vous que l'utilisation de la solution ci-dessus rendra une référence à une référence à ActionWrapper, qui ne sera pas collectée et peut générer des fuites de mémoire.



0
votes
public class Terminal : IDisposable
{
  List<IListener> _listeners;
  List<Action<string>> _sources;

  public Terminal(IEnumerable<IListener> listeners)
  {
      _listeners = new List<IListener>(listeners);
      _sources = new List<Action<string>>();
  }

  public void Subscribe(ref Action<string> source)
  {
      _sources.Add( source );
      source += Broadcast;
  }

  void Broadcast(string message)
  {
      foreach (var listener in _listeners) listener.Listen(message);
  }

  public void Dispose()
  {
      foreach ( var s in _sources ) s -= Broadcast; 
  }
}

0 commentaires

0
votes

edit:

Yep, mes mauvais délégués sont des types immuables, l'ajout d'une méthode à une liste d'invocation créera en réalité une nouvelle instance de délégué.

qui mène à une réponse pas à votre question. Pour vous désabonner au délégué, vous devez supprimer votre méthode de la liste d'invocation du délégué. Cela signifie créer un nouveau délégué et l'attribuer au champ d'origine ou à la variable. Mais vous ne pouvez pas accéder à l'original une fois que vous êtes sorti de abonnez-vous méthode. De plus, il peut y avoir d'autres copies de ce champ d'origine / variable ayant votre méthode sur la liste d'invocation. Et il n'ya aucun moyen de savoir à tous et de changer les valeurs.

Je suggère de déclarer une interface avec l'événement à votre usage. Ce sera une approche assez flexible. xxx

réponse originale

est une raison particulière pour laquelle vous passez source délégué comme ref ? Vous avez besoin de cela si vous voulez que vous souhaitiez retourner un délégué différent de la méthode.

Sinon, le délégué est un type de référence, vous pouvez donc vous y souscrire sans le transmettre comme ref ...


1 commentaires

Malheureusement, non, vous pouvez le tester aussi; Si vous ne passez pas l'instance du délégué par REF, vous vous abonnez-vous ne fonctionnera pas.



0
votes

Je suggérerais que la méthode d'abonnement devait renvoyer une mise en œuvre d'une classe d'abonnement, qui implémente Idisposable. Une implémentation simple serait pour l'abonnement à l'abonnement de tenir une référence à la liste d'abonnement et à une copie du délégué de l'abonnement; La liste d'abonnement elle-même serait une liste et la méthode d'élimination de l'abonnement à l'abonnement se supprimerait de la liste. Notez que si le même délégué est souscrit plusieurs fois, chaque abonnement retournera un abonnement différent; L'appel de l'appel d'un abonnement à une souscription annulera l'abonnement pour lequel il avait été retourné.

Une telle approche serait beaucoup plus propre que la méthode Délégate.combine / Délégate.ReMove utilisé par le modèle Normal .NET, dont la sémantique peut devenir très étrange si une tentative est faite pour s'abonner et désabonner des délégués multi-cibles.


2 commentaires

Je m'excuse, mais j'ai bien peur de ne pas comprendre: la partie critique pour moi tiendrait une référence au délégué initial afin de se désabonner de celui-ci (modifiant ainsi sa liste d'abonnement d'origine). Comment le système d'abonnement aurait-il une référence valide au délégué donné?


@ user1098567: Le constructeur de l'abonnement à l'abonnement prendrait le délégué comme paramètre. Étant donné que plusieurs abonnements utilisant le même délégué généreraient des objets d'abonnement distincts et que la liste des abonnements constituerait une liste de souscriptions plutôt que des délégués, appeler le décollage de l'objet renvoyé par une deuxième demande d'abonnement à l'aide d'un délégué particulier annulerait la deuxième souscription utilisée. Délégué, même le délégué avait été souscrit plus de fois après la deuxième demande d'abonnement.



0
votes

C'est assez simple, mais il y a quelques pièges. Si vous stockez une référence aux objets source, comme la plupart des exemples ont proposé jusqu'à présent, l'objet ne sera pas détesté collecté fort>. La meilleure façon d'éviter que cela consiste à utiliser une faiblesse de la mesure qui permettra à la GC de fonctionner correctement.

Donc, tout ce que vous avez à faire est celui-ci: p>

1) Ajouter une liste de sources à la classe: P>

public void Dispose()
{
    foreach (var r in _sources)
    {
        var source = (Action<string>) r.Target;
        if (source != null) 
        {
            source -= Broadcast;
            source = null;
        }
    }


    _sources.Clear();
}


2 commentaires

Bonjour, malheureusement, cela fait une différence si l'action est transmise en tant que réf. Si vous le transmettez comme une référence, il vous abonnera avec succès, mais en utilisant votre méthode, ne vous désabonnera pas. En passant sans mot-clé refer, il ne sera tout simplement pas abonné.


@ user1098567, après avoir lu l'autre réponse et effectuer des tests, j'ai compris le problème plus clairement. L'action n'est pas immuable (sinon, pas même en passant par REF fonctionnerait), mais c'est un apprécipé et en tant que tel, ne peut pas être cloné. C'était vraiment une belle question, m'a fait penser à des choses. Soyez juste prudent lorsque vous vous abonnez et utilisez des références faibles pour être en sécurité contre les fuites de mémoire.



0
votes

Peut-être, au lieu d'essayer de stocker une référence au délégué, apporter des appels à souscrire utilisez sa référence à l'objet avec le délégué pour créer des actions pour l'abonnement et la désinscription. C'est un paramètre supplémentaire, mais c'est toujours simple.

public event Action<string> CallOccured;

    public void Program()
    {
        Subscribe(a => CallOccured += a, a => CallOccured -= a);
        CallOccured("Hello");
    }


0 commentaires