Registering a handler for an event on the current

2019-04-12 03:36发布

问题:

I defined an event on my class and I want to make one of the methods of the class a handler for the event.

This is what I have so far:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegatesAndEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person();
            p.NameChangeEventHandler += new Person.NameChangeEventHandlerDel;

            p.Name = "Paul";
        }
    }

    class Person
    {
        #region Events
        public delegate void NameChangeEventHandlerDel(object sender, EventArgs args);
        public event EventHandler NameChangeEventHandler;
        protected void NameChange(EventArgs arg)
        {
            Console.WriteLine("Name change...");
        }
        #endregion

        #region Properties
        private string name;
        public string Name
        {
            get { return name; }
            set 
            {
                NameChange(null);
                name = value;
            }
        }
        #endregion

        public Person(string name = "John")
        {
            this.name = name;
        }
    }
}

How can I register the event handler without having to do it in Main?

回答1:

Have a read through How to: Publish Events that Conform to .NET Framework Guidelines for more information on how to create and use events. The example there isn't quite as clear as it should be so I'll walk through the process here.


The first part needed to define an event is the event handler delegate to be used. It's the method signature that is required for all who wishes to receive notifications on this event. You generally don't have to create a new delegate yourself, you should use the existing EventHandler (or generic EventHandler<TEventArgs>) delegate. You'd use the non-generic version if you don't need to include any other arguments about the event. Otherwise you'd use the generic version. In your case, there aren't any other arguments needed about the event so you should use the non-generic EventHandler.

(If you wanted to be able to include information like the old value and new value as arguments, you'd use the generic version with an appropriate class derived from EventArgs. A bit of an advanced topic so we'll skip that.)


The next part is to define the event. It's just as simple as if you were defining a property for your class. The difference here is that you'd use the event keyword to specify that you are defining an event followed by the delegate to use and the name of the event. The convention for naming the event for changing properties is usually in the pattern, PropertyNameChanged. Since you want an event that is fired when the Name property changes, you should name it: NameChanged.

public event EventHandler NameChanged;

An optional (but highly recommended) step is to define a method used to raise the event. This will make it easier for you to raise the event when you want. Usual naming convention is similar to how the event is named. This time, OnEventName. So you'd name it OnNameChanged here. It is usually defined as a protected virtual method that way derived classes could easily override this. The arguments to the function should be the arguments that is needed for the event. Since there are no arguments here, it could be left without arguments in the signature.

With that all in place, it's just a matter of calling the event handlers. It's just a delegate so just call on it. But don't forget to check if it's null first, that would signify that no event handlers were registered. The arguments to the handler should be this (the object that is raising the event) and whatever the arguments should be. In this case, there aren't arguments but you should return an instance of the "empty" EventArgs.

protected virtual void OnNameChanged()
{
    EventHandler nameChanged = NameChanged; // always a good idea to store in a local variable

    if (nameChanged != null)
    {
        nameChanged(this, new EventArgs());
    }
}

The final part is to wire this up in your properties. You'd want to raise the event if an assignment would change the value of the property. Pretty simple, just check to see if the old value is different from the new value. If it is, change it and raise the event. Otherwise, do nothing else.

private string name;
public string Name
{
    get { return name; }
    set 
    {
        if (!String.Equals(value, name)) // if the value gets changed...
        {
            name = value;
            OnNameChanged(); // raise the event!!!
        }
    }
}

Now that we're all set with the event, you want to be able to register some handlers for this event. To be able to do this, first we'd need the instance of the Person that we want to wait for Name changes and we need a event handling method with the right signature for the event. The event is defined to use the EventHandler delegate so we need a method with the signature: void NameChanged(object sender, EventArgs e). Remember, the sender argument is the object that raised the event. In this case, it is a Person object so we can get the object that changed and get inspect the properties if we wanted to. You can name this whatever you want. The pattern I go for personally is: InstanceName_EventName. So in this case, I'd name it: person_NameChanged.

static void person_NameChanged(object sender, EventArgs e)
{
    Person person = (Person)sender; // cast back to a Person so we can see what's changed

    Console.WriteLine("The name changed!");
    Console.WriteLine("It is now: " + person.Name); // let's print the name
}

Once that is defined, add the handler to the event and we'll be notified if anything changes.

person.NameChanged += person_NameChanged;

If you wanted the handler to be completely within the class, you could register the event within the class. No need to wire it up externally. You could do it from the constructor. I would not suggest adding your code to the OnNameChanged() event raising method, that method should be reserved for simply raising the event.

public Person(string name = "John")
{
    this.name = name;
    this.NameChanged += builtin_NameChanged;
}

private static void builtin_NameChanged(object sender, EventArgs e)
{
    Person person = (Person)sender; // cast back to a Person so we can see what's changed

    Console.WriteLine("The name changed!");
    Console.WriteLine("It is now: " + person.Name); // let's print the name
}

Note that in this particular example, the handler is static so it isn't tied to a single instance. It would work for any Person in general. Just be aware that since it is static, you would not be able to use this within the method which is why the cast on sender is necessary. Whether it is static or not is up to you ultimately, either way should be fine.


So to put this all together, this is what you could do:

class Person
{
    public Person(string name = "John")
    {
        this.name = name;
        this.NameChanged += builtin_NameChanged;
    }

    public string Name
    {
        get { return name; }
        set 
        {
            if (!String.Equals(value, name))
            {
                name = value;
                OnNameChanged();
            }
        }
    }

    public event EventHandler NameChanged;

    protected virtual void OnNameChanged()
    {
        EventHandler nameChanged = NameChanged;

        if (nameChanged != null)
        {
            nameChanged(this, new EventArgs());
        }
    }

    private static void builtin_NameChanged(object sender, EventArgs e)
    {
        Person person = (Person)sender;

        Console.WriteLine("The name changed!");
        Console.WriteLine("It is now: " + person.Name);
    }

    private string name;
}

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        person.Name = "Paul";
    }
}


回答2:

I changed and commented your code, but I'm not really sure about what you intend to do:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegatesAndEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person();
            //here you have an event of type EventHandler and want to subscribe with a delegate of type 
            //NameChangeEventHandlerDel. This can't work. Furthermore you have to create the delegate passing 
            //a method.
            //p.NameChangeEventHandler += new Person.NameChangeEventHandlerDel;
            // this could be better (even if I'm not sure about the necessity to use an event, 
            //but it probably depends on what you really are trying to do):
            p.NameChangeEventHandler += new EventHandler(p.NameChange);

            p.Name = "Paul";
        }
    }

    class Person
    {
        #region Events
        // why do you define the delegate NameChangeEventHandlerDel and then declare the event of type EventHandler?
        public delegate void NameChangeEventHandlerDel(object sender, EventArgs args);
        public event EventHandler NameChangeEventHandler;

        protected void NameChange(EventArgs arg)
        {
            Console.WriteLine("Name change...");
        }
        #endregion

        #region Properties
        private string name;
        public string Name
        {
            get { return name; }
            set 
            {
                // here you should not call your method directly, but trigger the event (if this is what you want)
                //i.e not: NameChange(null); but something like:
                if (NameChangeEventHandler != null)
                    NameChangeEventHandler(this, null);
                name = value;
            }
        }
        #endregion

        public Person(string name = "John")
        {
            this.name = name;
        }
    }
}


回答3:

Call a method in your person and start raising the events in that method. When ever a event is raised by the Person class, since the main has subscribed to this event. You get a call back shoot into Main.

You have just subscribed the event. Where is the event body??

If you rem the winforms events. You subscribe to lets say button click event like btn_Click(object, eventArgs).. Then you give a body for this event method right? Same way here.



回答4:

I don't really get what your intentions are, but:

  1. you have not subscribed a function in Program class that will handle the name change event.

  2. you have not fired the event in Person class. you should write:

    protected void NameChange(EventArgs arg) { Console.WriteLine("Name change..."); NameChangeEventHandler(); }