web-dev-qa-db-fra.com

Soulever un événement d'une classe d'une classe différente en C #

J'ai une classe, EventContainer.cs, qui contient un événement, disons:

public event EventHandler AfterSearch;

J'ai une autre classe, EventRaiser.cs. Comment est-ce que je soulève (et ne gère pas) l'événement susmentionné de cette classe?

L'événement déclenché appelle à son tour le gestionnaire de l'événement dans la classe EventContainer. Quelque chose comme ça (ce n'est évidemment pas correct):

EventContainer obj = new EventContainer(); 
RaiseEvent(obj.AfterSearch);
25
samar

Ce n'est pas possible, les événements ne peuvent être remontés que de l'intérieur de la classe. Si vous pouviez le faire, cela irait à l'encontre du but des événements (pouvoir augmenter les changements de statut de l'intérieur de la classe). Je pense que vous comprenez mal la fonction des événements - un événement est défini dans une classe et les autres peuvent s'y abonner en le faisant

obj.AfterSearch += handler; (où handler est une méthode correspondant à la signature de AfterSearch). On peut très bien s’abonner à l’événement de l’extérieur, mais on ne peut le remonter que de la classe qui le définit.

33
Femaref

C'est POSSIBLE, mais en utilisant un hack intelligent.

Inspiré par http://netpl.blogspot.com/2010/10/is-net-type-safe.html

Si vous ne croyez pas, essayez ce code.

using System;
using System.Runtime.InteropServices;

namespace Overlapping
{
    [StructLayout(LayoutKind.Explicit)]
    public class OverlapEvents
    {
        [FieldOffset(0)]
        public Foo Source;

        [FieldOffset(0)]
        public OtherFoo Target;
    }

    public class Foo
    {
        public event EventHandler Clicked;

        public override string ToString()
        {
            return "Hello Foo";
        }

        public void Click()
        {
            InvokeClicked(EventArgs.Empty);
        }

        private void InvokeClicked(EventArgs e)
        {
            var handler = Clicked;
            if (handler != null)
                handler(this, e);
        }
    }

    public class OtherFoo
    {
        public event EventHandler Clicked;

        public override string ToString()
        {
            return "Hello OtherFoo";
        }

        public void Click2()
        {
            InvokeClicked(EventArgs.Empty);
        }

        private void InvokeClicked(EventArgs e)
        {
            var handler = Clicked;
            if (handler != null)
                handler(this, e);
        }

        public void Clean()
        {
            Clicked = null;
        }
    }

    class Test
    {
        public static void Test3()
        {
            var a = new Foo();
            a.Clicked += AClicked;
            a.Click();
            var o = new OverlapEvents { Source = a };
            o.Target.Click2();
            o.Target.Clean();

            o.Target.Click2();
            a.Click();
        }

        static void AClicked(object sender, EventArgs e)
        {
            Console.WriteLine(sender.ToString());
        }
    }
}
20
halorty

Il semble que vous utilisiez le motif Délégué . Dans ce cas, l'événement AfterSearch doit être défini sur la classe EventRaiser et la classe EventContainer doit consommer l'événement:

In EventRaiser.cs

public event EventHandler BeforeSearch;
public event EventHandler AfterSearch;

public void ExecuteSearch(...)
{
    if (this.BeforeSearch != null)
      this.BeforeSearch();

    // Do search

    if (this.AfterSearch != null)
      this.AfterSearch();
}

In EventContainer.cs

public EventContainer(...)
{
    EventRaiser er = new EventRaiser();

    er.AfterSearch += this.OnAfterSearch;
}

public void OnAfterSearch()
{
   // Handle AfterSearch event
}
8
Mark Pim

Vous pouvez écrire une méthode publique sur la classe à partir de laquelle l'événement doit être déclenché et déclenche l'événement lorsqu'il est appelé. Vous pouvez ensuite appeler cette méthode à partir de n'importe quel utilisateur de votre classe.

Bien sûr, cette ruine encapsulation et est mauvaise conception.

7
Oded

Ce n'est pas une bonne programmation, mais si vous voulez le faire, vous pouvez faire quelque chose comme ça.

class Program
{
    static void Main(string[] args)
    {

        Extension ext = new Extension();
        ext.MyEvent += ext_MyEvent;
        ext.Dosomething();
    }

    static void ext_MyEvent(int num)
    {
        Console.WriteLine(num);
    }
}


public class Extension
{
    public delegate void MyEventHandler(int num);
    public event MyEventHandler MyEvent;

    public void Dosomething()
    {
        int no = 0;
        while(true){
            if(MyEvent!=null){
                MyEvent(++no);
            }
        }
    }
}
4
Jagdish

Il y a un bon moyen de le faire. Chaque événement en C # a un délégué qui spécifie le signe des méthodes pour cet événement. Définissez un champ dans votre classe externe avec le type de votre délégué à l'événement. récupère la référence de ce champ dans le constructeur de la classe externe et le sauvegarde. Dans la classe principale de votre événement, envoyez la référence de l'événement au délégué de la classe externe. Maintenant, vous pouvez facilement appeler le délégué de votre classe externe.

public delegate void MyEventHandler(object Sender, EventArgs Args);

public class MyMain
{
     public event MyEventHandler MyEvent;
     ...
     new MyExternal(this.MyEvent);
     ...
}

public MyExternal
{
     private MyEventHandler MyEvent;
     public MyExternal(MyEventHandler MyEvent)
     {
           this.MyEvent = MyEvent;
     }
     ...
     this.MyEvent(..., ...);
     ...
}
4
Milad Irannejad

D'accord avec Femaref - et notez qu'il s'agit d'une différence importante entre les délégués et les événements (voir par exemple cette entrée de blog pour une bonne discussion de cela et d'autres différences).

Selon ce que vous voulez réaliser, vous pourriez être mieux avec un délégué.

3
Tim Barrass

Je suis tombé sur ce problème aussi, parce que j'essayais d'appeler des événements PropertyChanged de l'extérieur. Donc, vous n'avez pas à tout mettre en œuvre dans chaque classe. La solution de halorty ne fonctionnerait pas avec des interfaces.

J'ai trouvé une solution en utilisant une réflexion intense. Cela est sûrement lent et brise le principe voulant que les événements ne soient appelés que de l'intérieur d'une classe. Mais il est intéressant de trouver une solution générique à ce problème .... 

Cela fonctionne parce que chaque événement est une liste de méthodes d'appel appelées. Ainsi, nous pouvons obtenir la liste d’invocations et appeler chaque auditeur associé à cet événement.

Voici....

class Program
{
  static void Main(string[] args)
  {
    var instance = new TestPropertyChanged();
    instance.PropertyChanged += PropertyChanged;

    instance.RaiseEvent(nameof(INotifyPropertyChanged.PropertyChanged), new PropertyChangedEventArgs("Hi There from anywhere"));
    Console.ReadLine();
  }

  private static void PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    Console.WriteLine(e.PropertyName);
  }
}

public static class PropertyRaiser
{
  private static readonly BindingFlags staticFlags = BindingFlags.Instance | BindingFlags.NonPublic;

  public static void RaiseEvent(this object instance, string eventName, EventArgs e)
  {
    var type = instance.GetType();
    var eventField = type.GetField(eventName, staticFlags);
    if (eventField == null)
      throw new Exception($"Event with name {eventName} could not be found.");
    var multicastDelegate = eventField.GetValue(instance) as MulticastDelegate;
    if (multicastDelegate == null)
      return;

    var invocationList = multicastDelegate.GetInvocationList();

    foreach (var invocationMethod in invocationList)
      invocationMethod.DynamicInvoke(new[] {instance, e});
  }
}

public class TestPropertyChanged : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
}
1
feal

J'ai eu une confusion similaire et honnêtement, je trouve que les réponses sont déroutantes. Bien qu'un couple ait laissé entendre que des solutions que je trouverais plus tard fonctionneraient.

Ma solution a été de taper dans les cahiers des charges et de mieux connaître les délégués et les gestionnaires d’événements . Même si je les utilise tous les deux depuis de nombreuses années, je ne les ai jamais connus intimement. http://www.codeproject.com/Articles/20550/C-Event-Implementation-Fundamentals-Best- Practices donne la meilleure explication que j'ai jamais eue des délégués et des gestionnaires d'événements lire et expliquer clairement qu’une classe peut être un éditeur d’événements et que d’autres classes la consomment . Cet article: http://www.codeproject.com/Articles/12285/Implementing-an-event-which- supports-only-a-single explique comment diffuser en un seul événement des événements, les délégués étant multidiffusion par définition. Un délégué hérite de system.MulticastDelegate. La plupart, y compris les délégués système, sont multicast .J'ai constaté que la multidiffusion signifiait que tout gestionnaire d'événement avec la même signature recevrait l'événement déclenché. Le comportement de multidiffusion m'a causé des nuits blanches alors que je parcourais le code et voyais mon événement apparemment envoyé à tort à des gestionnaires que je n'avais pas l'intention d'obtenir de cet événement. Les deux articles expliquent ce comportement. Le deuxième article vous montre une manière, et le premier article vous en montre une autre, en rendant le délégué et la signature bien dactylographiés. Personnellement, je crois que le dactylographie forte empêche les bugs stupides qui peuvent être difficiles à trouver. Je voterais donc pour le premier article, même si le code de second article fonctionnait correctement. J'étais juste curieux. :-) 

Je me suis aussi demandé si je pouvais obtenir que le code d'articles n ° 2 se comporte comme mon interprétation de la question initiale ci-dessus. Quelle que soit l'approche choisie ou si j'interprète mal la question initiale, mon vrai message est que je pense toujours que vous auriez intérêt à lire le premier article comme je l'ai fait, surtout si les questions ou les réponses sur cette page vous laissent confus. Si vous rencontrez des cauchemars de multidiffusion et que vous avez besoin d’une solution rapide, l’article 2 peut vous aider.

J'ai commencé à jouer avec la classe eventRaiser du deuxième article. J'ai fait un simple projet de formulaire Windows. J'ai ajouté la deuxième classe d'articles EventRaiser.cs à mon projet . Dans le code du formulaire principal, j'ai défini une référence à cette classe EventRaiser en haut de la page. 

private EventRaiser eventRaiser = new EventRaiser();

J'ai ajouté une méthode dans le code de formulaire principal que je voulais appeler lorsque l'événement était déclenché

protected void MainResponse( object sender, EventArgs eArgs )
{            
    MessageBox.Show("got to MainResponse");
}

puis dans le constructeur du formulaire principal, j'ai ajouté l'affectation d'événement: 

eventRaiser.OnRaiseEvent += new EventHandler(MainResponse);`

J'ai ensuite créé une classe qui serait instanciée par mon formulaire principal appelé "SimpleClass" par manque d'ingéniosité créative pour le moment.

Puis j'ai ajouté un bouton et dans l'événement click du bouton J'ai instancié le code SimpleClass à partir duquel je voulais déclencher un événement:

    private void button1_Click( object sender, EventArgs e )
   {            
       SimpleClass sc = new SimpleClass(eventRaiser);
   }

Notez l'instance de "eventRaiser" que j'ai transmise à SimpleClass.cs. Cela a été défini et instancié plus tôt dans le code de formulaire principal.

Dans la SimpleClass:

using System.Windows.Forms;
using SinglecastEvent; // see SingleCastEvent Project for info or http://www.codeproject.com/Articles/12285/Implementing-an-event-which-supports-only-a-single

    namespace GenericTest
    {

        public class SimpleClass
        {

            private EventRaiser eventRaiser = new EventRaiser();

            public SimpleClass( EventRaiser ev )
            {
                eventRaiser = ev;
                simpleMethod();

            }
            private void simpleMethod()
            {

                MessageBox.Show("in FileWatcher.simple() about to raise the event");
                eventRaiser.RaiseEvent();
            }
        }
    }

Le seul point sur la méthode privée que j’ai appelée SimpleMethod était de vérifier qu’une méthode de portée privée pouvait toujours déclencher l’événement, non pas que j'en doutais, mais que j’aimerais être positive.

J'ai dirigé le projet et cela a abouti à élever l'événement du "simpleMethod" de "SimpleClass" jusqu'au formulaire principal et à passer à la méthode correcte attendue appelée MainResponse, prouvant qu'une classe peut effectivement déclencher un événement consommé par un autre. class . Oui, l'événement doit être déclenché au sein de la classe qui a besoin de la diffuser et la diffuser aux autres classes concernées. Les classes réceptrices peuvent être une ou plusieurs classes en fonction de la force avec laquelle vous les avez définies ou en les rendant uniques, comme dans le 2ème article.

J'espère que cela aide et ne pas brouiller l'eau. Personnellement, j'ai beaucoup de délégués et d'événements à nettoyer! Les démons de multidiffusion commencent!

0
user3866622

Exemple très simple. J'aime le faire de cette façon en utilisant EventHandler .

    class Program
    {
        static void Main(string[] args)
        {
            MyExtension ext = new MyExtension();
            ext.MyEvent += ext_MyEvent;
            ext.Dosomething();
            Console.ReadLine();
        }

        static void ext_MyEvent(object sender, int num)
        {
            Console.WriteLine("Event fired.... "+num);
        }
    }

    public class MyExtension
    {
        public event EventHandler<int> MyEvent;

        public void Dosomething()
        {
            int no = 1;

            if (MyEvent != null)
                MyEvent(this, ++no);
        }
    }
}
0
Mist

La classe qui élève doit obtenir une nouvelle copie de la variable EventHandler.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        class HasEvent
        {
            public event EventHandler OnEnvent;
            EventInvoker myInvoker;

            public HasEvent()
            {
                myInvoker = new EventInvoker(this, () => OnEnvent);
            }

            public void MyInvokerRaising() {
                myInvoker.Raise();
            }

        }

        class EventInvoker
        {
            private Func<EventHandler> GetEventHandler;
            private object sender;

            public EventInvoker(object sender, Func<EventHandler> GetEventHandler)
            {
                this.sender = sender;
                this.GetEventHandler = GetEventHandler;
            }

            public void Raise()
            {
                if(null != GetEventHandler())
                {
                    GetEventHandler()(sender, new EventArgs());
                }
            }
        }

        static void Main(string[] args)
        {
            HasEvent h = new HasEvent();
            h.OnEnvent += H_OnEnvent;
            h.MyInvokerRaising();
        }

        private static void H_OnEnvent(object sender, EventArgs e)
        {
            Console.WriteLine("FIRED");
        }
    }
}
0
Johannes