2010-08-30 3 views
0

C# n'est toujours pas assez OO? Ici, je donne un exemple (peut-être mauvais).Traiter les événements comme des objets

public class Program 
{ 
    public event EventHandler OnStart; 
    public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts"); 

    public class MyCSharpProgram 
    { 
     public string Name { get; set; } 
     public event EventHandler OnStart; 
     public void Start() 
     { 
      OnStart(this, EventArgs.Empty); 
     } 
    } 

    static void Main(string[] args) 
    { 
     MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" }; 
     cs.OnStart += LogOnStart; //can compile 
     //RegisterLogger(cs.OnStart); // Line of trouble 
     cs.Start(); // it prints "start" (of course it will :D) 

     Program p = new Program(); 
     RegisterLogger(p.OnStart); //can compile 
     p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime 
     Console.Read(); 
    } 

    static void RegisterLogger(EventHandler ev) 
    { 
     ev += LogOnStart; 
    } 
} 

RegisterLogger (cs.OnStart) conduit à compiler une erreur, parce que "Le XXX événement ne peut apparaître sur le côté gauche de + = ou - = blabla". Mais pourquoi RegisterLogger (p.OnStart) peut-il? Pendant ce temps, bien que j'ai enregistré p.OnStart, il va également lancer une exception NullReferenceException, semble que p.OnStart n'est pas "vraiment" passé à une méthode.

+0

Notez que même s'il n'est pas possible de transmettre des "références d'événement", vous pouvez contourner cela en encapsulant tout ce que vous voulez faire avec l'événement dans un délégué. Mon article sur http://powderize.ch/c-event-relays/ explore une application de cette idée pour créer un relais d'événement. Peut-être que vous pouvez prendre cette idée et l'adapter à vos besoins! – theDmi

Répondre

1

"L'événement XXX ne peut apparaître sur le côté gauche de + = ou - = blabla"

Cette est en fait parce que C# est "OO assez." L'un des principes fondamentaux de la POO est l'encapsulation; Les événements fournissent une forme de ceci, tout comme les propriétés: à l'intérieur de la classe déclarante, ils peuvent être accédés directement, mais à l'extérieur, ils sont seulement exposés aux opérateurs += et -=. Cela permet à la classe déclarante de contrôler totalement le moment où les événements sont appelés.Le code client peut seulement avoir son mot à dire dans ce qui se passe quand ils sont appelés.

La raison pour laquelle votre code RegisterLogger(p.OnStart) compile est qu'il est déclarée à partir dans le cadre de la classe Program, où l'événement Program.OnStart est déclarée.

La raison pour laquelle votre code RegisterLogger(cs.OnStart) ne pas est que la compilation elle est déclarée à partir dans le cadre de la classe Program, mais l'événement MyCSharpProgram.OnStart est déclarée (évidemment) dans la classe MyCSharpProgram.

Comme Chris Taylor rappelle, la raison pour laquelle vous obtenez un NullReferenceException sur la ligne p.OnStart(p, EventArgs.Empty); est que l'appel RegisterLogger comme vous l'avez attribue une nouvelle valeur à une variable locale, ayant aucun effet sur l'objet auquel cette variable locale a été affecté quand il a été passé en paramètre. Pour mieux comprendre cela, considérez le code suivant:

static void IncrementValue(int value) 
{ 
    value += 1; 
} 

int i = 0; 
IncrementValue(i); 

// Prints '0', because IncrementValue had no effect on i -- 
// a new value was assigned to the COPY of i that was passed in 
Console.WriteLine(i); 

Tout comme une méthode qui prend un int comme paramètre et attribue une nouvelle valeur à elle affecte uniquement la variable locale copiée sur sa pile, une méthode qui prend un EventHandler en tant que paramètre et attribue une nouvelle valeur à affecte uniquement sa variable locale ainsi (dans une affectation).

+0

Il suffit de revoir cette question et de trouver quelque chose d'intéressant. Comme vous l'avez dit "une méthode qui prend un EventHandler comme paramètre et lui assigne une nouvelle valeur n'affecte que sa variable locale", c'est correct. Mais mon code "ev + = LogOnStart" n'attribue pas, c'est "ev = ev + LogOnStart". Donc je vérifie l'opérateur add pour 'System.Delegate' (enfin, vous trouverez la méthode CombineImpl dans' System.MulticastDelegate'), cette méthode retourne toujours une nouvelle instance de délégué, donc 'ev + = xxx' n'affectera pas la variable à l'extérieur. Si la méthode renvoie l'instance d'origine, tout est différent alors. –

+0

Quoi qu'il en soit, le dernier commentaire est un petit épisode, pas très relatif à ma question d'origine. Juste poster pour votre information. –

+0

@DannyChen: Je ne suis pas sûre de te suivre. L'opérateur '+ =' * * est * assignation, dans le contexte dont nous parlons. Étant donné toute variable locale arbitraire 'x', l'expression' x + = y' constitue une affectation dont la valeur dépend de l'implémentation de l'opérateur '+' pour le type 'x'. Bien sûr, pour * events *, les choses sont un peu différentes puisque '+ =' est compilé avec un appel à 'add' de l'événement, ce qui pourrait être une implémentation personnalisée. Mais c'est un autre problème, en fait, puisque dans votre méthode 'RegisterLogger' nous avons affaire à un' 'EventHandler '' plutôt qu'à un événement .NET. –

1

Apportez les modifications suivantes à RegisterLogger, déclarez ev comme argument de référence du gestionnaire d'événements.

static void RegisterLogger(ref EventHandler ev) 
{ 
    ev += LogOnStart; 
} 

Ensuite, votre point d'appel devra également utiliser le 'ref' mot-clé lors de l'appel de la méthode comme suit

RegisterLogger(ref p.OnStart); 
+0

Oui NullReference ne sortira pas maintenant. Mais même si j'ajoute 'ref',' RegisterLogger (cs.OnStart) 'ne fonctionnera pas. –

+0

@Danny Chen, j'ai abordé votre deuxième problème, la raison pour laquelle le premier appel ne fonctionnera pas est qu'en dehors de la classe déclarante, un événement ne peut être utilisé que pour ajouter de nouveaux gestionnaires. l'événement à déclencher en invoquant l'événement directement, seule la fonctionnalité Ajout/Suppression de l'événement est exposée en dehors de la classe déclarante. –

+0

Ah, je viens d'apprendre que les variables déléguées vivent sur la pile. Bon à savoir. :) –

1

La raison pour laquelle ce ne peut pas compiler:

RegisterLogger (cs .OnStart);

... est que le gestionnaire d'événements et la méthode que vous transmettez sont dans des classes différentes. C# traite les événements de manière très stricte et n'autorise que la classe dans laquelle l'événement apparaît à faire autre chose que d'ajouter un gestionnaire (y compris le transmettre aux fonctions ou l'invoquer).

Par exemple, cela ne compile pas non plus (parce qu'il est dans une classe différente):

cs.OnStart(cs, EventArgs.Empty); 

Comme pour ne pas être en mesure de passer un gestionnaire d'événements à une fonction de cette façon, je ne suis pas sûr. Je suppose que les événements fonctionnent comme des types de valeur. En passant par ref va résoudre votre problème, cependant:

static void RegisterLogger(ref EventHandler ev) 
{ 
    ev += LogOnStart; 
} 
+0

pourquoi C# ne nous permet-il pas de transmettre des événements à d'autres classes? Pourquoi si strictement? –

+0

@Danny: Demandez à l'équipe de conception C#, je suppose? Ça m'a frustré plusieurs fois aussi. –

+0

@Danny: les événements ne sont pas des * valeurs *. De même, les propriétés, les champs, les indexeurs, les classes, les structures, les interfaces, les énumérations, les méthodes, les attributs, les variables locales, les paramètres formels et les paramètres de type ne sont pas des valeurs. Vous ne pouvez pas passer une * propriété *. Vous pouvez passer la * valeur * qui résulte de l'appel d'une propriété getter, mais vous ne pouvez pas passer * la propriété *. Vous ne pouvez pas passer un * événement *; vous pouvez passer la * valeur * du délégué associé à l'événement, mais l'événement lui-même n'est pas une valeur plus qu'une propriété est une valeur. –

0

Lorsqu'un objet déclare un événement, il expose que des méthodes pour ajouter et/ou supprimer des gestionnaires à l'événement en dehors de la classe (à condition qu'elle ne redéfinit pas les opérations d'ajout/suppression). En son sein, il est traité comme un «objet» et fonctionne plus ou moins comme une variable déléguée déclarée. Si aucun gestionnaire n'est ajouté à l'événement, c'est comme s'il n'avait jamais été initialisé et qu'il est null. C'est ainsi par conception. Voici le schéma typique utilisé dans le cadre:

public class MyCSharpProgram 
{ 
    // ... 

    // define the event 
    public event EventHandler SomeEvent; 

    // add a mechanism to "raise" the event 
    protected virtual void OnSomeEvent() 
    { 
     // SomeEvent is a "variable" to a EventHandler 
     if (SomeEvent != null) 
      SomeEvent(this, EventArgs.Empty); 
    } 
} 

// etc... 

Maintenant, si vous devez insister sur exposer le délégué à l'extérieur de votre classe, juste ne définissent pas comme un événement. Vous pourriez alors le traiter comme n'importe quel autre champ ou propriété.

J'ai modifié votre code d'exemple pour illustrer:

public class Program 
{ 
    public EventHandler OnStart; 
    public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts"); 

    public class MyCSharpProgram 
    { 
     public string Name { get; set; } 

     // just a field to an EventHandler 
     public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks 
     public void Start() 
     { 
      // always check if non-null 
      if (OnStart != null) 
       OnStart(this, EventArgs.Empty); 
     } 
    } 

    static void Main(string[] args) 
    { 
     MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" }; 
     cs.OnStart += LogOnStart; //can compile 
     RegisterLogger(cs.OnStart); // should work now 
     cs.Start(); // it prints "start" (of course it will :D) 

     Program p = new Program(); 
     RegisterLogger(p.OnStart); //can compile 
     p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime 
     Console.Read(); 
    } 

    static void RegisterLogger(EventHandler ev) 
    { 
     // Program.OnStart not initialized so ev is null 
     if (ev != null) //null-check just in case 
      ev += LogOnStart; 
    } 
} 
Questions connexes