locked
Event and Delegate Question RRS feed

  • Question

  • Hi,

    In an attempt to get my head around how events and delegates work in C#, I have created a project with two classes clsSQLServer and clsOracle

     

     

    I have also created three events. Two use the default  EventHandler delegate and the other uses a custom DatabaseUpadateEventHandler delegate.

     public interface IDatabase
        {
            void Update(string message);
            event EventHandler Start;
            event EventHandler Ended;
            event DatabaseUpadateEventHandler MessageSet;
        }

     public class DatabaseUpadateEventArgs : EventArgs
        {
            public string Message;

            public DatabaseUpadateEventArgs(string message)
            {
                this.Message = message;
            }
        }

    I have implemented it using the OnEventName method for each event. However, I have seen some code online where people use events like this and do not have an OnEventName method:

    event EventHandler<MonthArgs> MonthsLoaded;

    public class MonthArgs : EventArgs
    {
          
            public List<Month> Months { get; set; }

            public MonthArgs()
            {
                Months = new List<Month>();
            }
    }

    service.MonthsLoaded += new EventHandler<MonthArgs>(service_MonthsLoaded); 

    I would like to understand what is the best way to do it and what the difference is. Also, how could I change my implementation to use this new method? Please can someone shed some ideas and code?

    Thanks

    My implementation code: 

    public class clsSQLServer : IDatabase
        {
            public event EventHandler Start;

            protected virtual void OnStart(EventArgs e)
            {
                if (this.Start != null)
                {
                    this.Start(this, e);
                }
            }

            public event EventHandler Ended;

            protected virtual void OnEnded(EventArgs e)
            {
                if (this.Ended != null)
                {
                    this.Ended(this, e);
                }
            }

            //public delegate void DatabaseUpadateEventHandler(object sender, DatabaseUpadateEventArgs e);
            public event DatabaseUpadateEventHandler MessageSet;

            protected virtual void OnMessageSet(DatabaseUpadateEventArgs e)
            {
                if (this.MessageSet != null)
                {
                    this.MessageSet(this, e);
                }
            }

            public void Update(string message)
            {
                // CODE TO update clsSQLServer db
                // Raise event to notify others
                this.OnStart(new EventArgs());
                this.OnMessageSet(new DatabaseUpadateEventArgs(message));
                System.Threading.Thread.Sleep(2000);
                this.OnEnded(new EventArgs());
          
            }
        } 

    ++++

    Here I have to subscribe to the event twice so that both object raise it. I do not think that is right. Any ideas?

    Also, I am not sure where I have to remove the event. I have tried to remove it on the handler function but I cannot see if there.

    private void button1_Click(object sender, EventArgs e)
            {
                IDatabase objDb;
                objDb = new clsSQLServer();

                objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet);

                clsCustomerObject obj = new clsCustomerObject();
                obj.Update(objDb);
                objDb = new clsOracle();

                objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet);

                obj.Update(objDb);

              
            }

            void objDb_MessageSet(object sender, DatabaseUpadateEventArgs e)
            {
                IDatabase obj = sender as IDatabase;
                //obj.MessageSet -=
                MessageBox.Show("Update button for " + e.Message);
            }

     

    Friday, 21 May 2010 1:46 PM

Answers

  • It is standard practice to always include the base class name, EventArgs, in classes that inherit from it.


        public class MonthEventArgs : EventArgs
        {
            public List<Month> Months
            {
                get;
                set;
            }

            public MonthEventArgs()
            {
                Months = new List<Month>();
            }
        }
       
        public class Month
        {
        }

        public class ServerService
        {
            public event EventHandler<MonthEventArgs > MonthsLoaded;
        }

        public class MonthTester
        {
            ServerService service;

            public MonthTester()
            {
                service = new ServerService();
                service.MonthsLoaded += new EventHandler<MonthEventArgs >(service_MonthsLoaded);
            }

            void service_MonthsLoaded(object sender, MonthEventArgs e)
            {
                throw new NotImplementedException();
            }
        }


    Using the generic event handler simply forces the handler signature to use a custom EventArgs class, instead of the base one.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, 28 May 2010 3:02 PM
  •  

    Here I have to subscribe to the event twice so that both object raise it. I do not think that is right. Any ideas?

    Yes, you made another object reference...

    objDb = new clsSQLServer();

                objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet); // 1 handler on objDB of type SQL

    Then you made another one of type clsOracle...

    objDb = new clsOracle();

    objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet); // 1 handler added to new object of type Oracle.

    Also, I am not sure where I have to remove the event. I have tried to remove it on the handler function but I cannot see if there.

    Not sure why you want to remove any events, but to generally answer your question you can check to see if a handler is registered by checking the invocation list.

    From http://stackoverflow.com/questions/937181/c-pattern-to-prevent-an-event-handler-hooked-twice

    You could check the invocation list. You'll also need to check for null:

    private EventHandler foo; 
    public event EventHandler Foo 
    { 
        add 
        { 
            if (foo == null || !foo.GetInvocationList().Contains(value)) 
            { 
                foo += value; 
            } 
        } 
        remove 
        { 
            foo -= value; 
        } 
    } 
    
    Hope it helps.
    Friday, 28 May 2010 3:56 PM
  • Hi John,

    You said: "never make the public fields with a set"

    Could you please explain why so that I can understand it?

    Cheers

     

     

    Because when an event gets triggered, the event arguments (If there are any), are merely to give subscribers information about who triggered the event and pass along any additional information that the event arg extended class is provided to give. It is not meant for you to "change", but is only "informational"

    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 3:07 PM

All replies

  • Hi,

    I'm not sure what do you mean with these events, what is the problem? what do you want to achieve?

    Harry


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Friday, 28 May 2010 5:43 AM
  • Hi Harry,

    I would like to understand how to create an event based on generics.

    I know how to create events and delegates using the old style of creating a method like OnSomethingHappen()

    But I have seen some source code where people use a custom class that inheirts EvenArgs.

    event EventHandler<MonthArgs> MonthsLoaded;

    public class MonthArgs : EventArgs
    {
          
            public List<Month> Months { get; set; }

            public MonthArgs()
            {
                Months = new List<Month>();
            }
    }

    service.MonthsLoaded += new EventHandler<MonthArgs>(service_MonthsLoaded); 

    I would like to understand how it works and what is the best way to do it and what the difference is.

    Cheers

     

     

     

     

    Friday, 28 May 2010 2:48 PM
  • It is standard practice to always include the base class name, EventArgs, in classes that inherit from it.


        public class MonthEventArgs : EventArgs
        {
            public List<Month> Months
            {
                get;
                set;
            }

            public MonthEventArgs()
            {
                Months = new List<Month>();
            }
        }
       
        public class Month
        {
        }

        public class ServerService
        {
            public event EventHandler<MonthEventArgs > MonthsLoaded;
        }

        public class MonthTester
        {
            ServerService service;

            public MonthTester()
            {
                service = new ServerService();
                service.MonthsLoaded += new EventHandler<MonthEventArgs >(service_MonthsLoaded);
            }

            void service_MonthsLoaded(object sender, MonthEventArgs e)
            {
                throw new NotImplementedException();
            }
        }


    Using the generic event handler simply forces the handler signature to use a custom EventArgs class, instead of the base one.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, 28 May 2010 3:02 PM
  • When extending the EventArg class, never make the public fields with a "set". Make it "get" only and and a private set and populate it in the constructor upon instantiation.

    The purpose of the event is to let the subscribers know that it was triggered and to "give" the various information associated with it.


    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Friday, 28 May 2010 3:50 PM
  •  

    Here I have to subscribe to the event twice so that both object raise it. I do not think that is right. Any ideas?

    Yes, you made another object reference...

    objDb = new clsSQLServer();

                objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet); // 1 handler on objDB of type SQL

    Then you made another one of type clsOracle...

    objDb = new clsOracle();

    objDb.MessageSet += new DatabaseUpadateEventHandler(objDb_MessageSet); // 1 handler added to new object of type Oracle.

    Also, I am not sure where I have to remove the event. I have tried to remove it on the handler function but I cannot see if there.

    Not sure why you want to remove any events, but to generally answer your question you can check to see if a handler is registered by checking the invocation list.

    From http://stackoverflow.com/questions/937181/c-pattern-to-prevent-an-event-handler-hooked-twice

    You could check the invocation list. You'll also need to check for null:

    private EventHandler foo; 
    public event EventHandler Foo 
    { 
        add 
        { 
            if (foo == null || !foo.GetInvocationList().Contains(value)) 
            { 
                foo += value; 
            } 
        } 
        remove 
        { 
            foo -= value; 
        } 
    } 
    
    Hope it helps.
    Friday, 28 May 2010 3:56 PM
  • Hi IAmLouis,

    Could you plese elaborate abit more on your code.

    How would I use that?

    As for your question why I want to remove the event handler, I have seen people always removing the event after calling a WCF service which is asynchronous. What am I missing here?

    Cheers

     

    Friday, 28 May 2010 11:01 PM
  • Hi John,

    You said: "never make the public fields with a set"

    Could you please explain why so that I can understand it?

    Cheers

     

     

    Friday, 28 May 2010 11:03 PM
  • Hi Rudy,

    Thanks for the reply and souce code.

    Am I right to say that using the generic event handler we have to write less code because we do not need to right the OnMessageSet() function?

     public event DatabaseUpadateEventHandler MessageSet;

            protected virtual void OnMessageSet(DatabaseUpadateEventArgs e)
            {
                if (this.MessageSet != null)
                {
                    this.MessageSet(this, e);
                }
            } 

    Friday, 28 May 2010 11:09 PM
  • No.  You must write code to actually Invoke the event.  The IDE cannot write this method for you, just as it does not fill in the body of any method for you.  It leaves a single line throwing a NotImplementedException, instead. 

    The generic event handler has been provided in the Framework because it is not possible to define your own.  At least it isn't easy.  It also provides a path to a standardized generic way of implementing the APM, Asynchronous Programming Model.  Creating your own generic delegate would a difficult and overly time-consuming.

    Clients and developers that use the .NET Framework should strive to conform to the naming conventions used throughout the BCL for various types of purposes.  You appear to have picked up on the naming convention for methods that fire events, OnEventName .  Similar method naming conventions can be found in the APM with the complimentary BeginXXXX and EndXXXX methods.

    It is also recommended that one should use the explicit syntax to invoke a delegate.  That means calling the Invoke method.  This provides the person who must maintain the code down the road an obvious visual cue that a delegate is being Invoked, instead of a mere method call. 

    The syntax that you used is perfectly valid, and many programmers argue that they find it just as easy to recognize.  The recognition only comes with the pre-cognition of the variable name.  They knew the name of the event ahead of time, which made it easy to recognize.

    Rudy  =8^D


    Mark the best replies as answers. "Fooling computers since 1971."
    Tuesday, 1 June 2010 1:59 PM
  • The generic event handler has been provided in the Framework because it is not possible to define your own.  At least it isn't easy.
    Creating your own generic delegate would a difficult and overly time-consuming.

    What is difficult in creating a generic delegate?

    public delegate void MyOwnGenericDelegate<T>(object sender, T e) where T : EventArgs;

    Tuesday, 1 June 2010 2:23 PM
  • Hi John,

    You said: "never make the public fields with a set"

    Could you please explain why so that I can understand it?

    Cheers

     

     

    Because when an event gets triggered, the event arguments (If there are any), are merely to give subscribers information about who triggered the event and pass along any additional information that the event arg extended class is provided to give. It is not meant for you to "change", but is only "informational"

    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 3:07 PM
  • The generic event handler has been provided in the Framework because it is not possible to define your own.  At least it isn't easy.
    Creating your own generic delegate would a difficult and overly time-consuming.

    What is difficult in creating a generic delegate?

    public delegate void MyOwnGenericDelegate<T>(object sender, T e) where T : EventArgs;


    Nothing.  I simply did not want to open the door that gets away from the standard event model in the BCL.

    Mark the best replies as answers. "Fooling computers since 1971."
    Tuesday, 1 June 2010 3:12 PM
  • I use generic delegates every time I create a delegate that has any kind of arguments whatsoever. If you have one that has no arguments, you can use of course the MethodInvoker delegate or if it is necessary to at least provide the sender, then a simple EventHandler will suffice.
    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 3:40 PM
  • Because when an event gets triggered, the event arguments (If there are any), are merely to give subscribers information about who triggered the event and pass along any additional information that the event arg extended class is provided to give. It is not meant for you to "change", but is only "informational"

    You mean, like FormClosingEventArgs.Cancel?
    Tuesday, 1 June 2010 3:40 PM
  • Because when an event gets triggered, the event arguments (If there are any), are merely to give subscribers information about who triggered the event and pass along any additional information that the event arg extended class is provided to give. It is not meant for you to "change", but is only "informational"

    You mean, like FormClosingEventArgs.Cancel?

    I never considered this, good point. Can we think of any others, I am at a loss.

    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 3:57 PM
  • Somehow similar to Cancel, you have:

    KeyEventArgs.Handled and SuppressKeyPress

    And you have also cases where the handler can modify its input:

    KeyPressEventArgs.KeyChar

    Tuesday, 1 June 2010 4:05 PM
  • Somehow similar to Cancel, you have:

    KeyEventArgs.Handled and SuppressKeyPress

    And you have also cases where the handler can modify its input:

    KeyPressEventArgs.KeyChar


    I didn't know you could modify KeyChar, I had always believed it was simply passing that and you could therefore determined what key was pressed.

    I think the thousands of events there are and the fact we can name "two" suggests that my premise was true, though not "exclusively" true.


    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 4:10 PM
  • John,

    Sure, you can modify EventArgs properties.  It is one way to pass along info, or 'return' objects back to the publisher. 

    As far as modifying e.KeyChar goes, do you remember that RegexTextBox I had posted 3 years ago?  It would read the keystrokes to see if they were valid.  With the exception of the Backspace, any invalid keystrokes were changed to Backspace to erase the character the user just typed from the TextBox. 

    If you had a Phone Number box, something where letters are invalid, any typed letters would result in nothing appearing in the TextBox.  Only digits would show up.


    Mark the best replies as answers. "Fooling computers since 1971."
    Tuesday, 1 June 2010 4:24 PM
  • That is similar to this:

    //Example of a numeric textbox
    public class CustomTextBox : System.Windows.Forms.TextBox
    {
          protected override void OnKeyPress(KeyPressEventArgs e)
          {
                base.OnKeyPress(e);
                if (char.IsNumber(e.KeyChar))
                    e.Handled = true;
          }
    }


    John Grove - TFD Group, Senior Software Engineer, EI Division, http://www.tfdg.com
    Tuesday, 1 June 2010 4:37 PM
  • Yeah, just make sure that you call base.OnKeyPress(e) last! 

    However, even that will still leave the typed alpha characters behind.  Change e.KeyChar to a backspace, too.


    Mark the best replies as answers. "Fooling computers since 1971."
    Tuesday, 1 June 2010 4:58 PM
  • An event is a special kind of delegate that facilitates event-driven programming. Events are class members that cannot be called outside of the class regardless of its access specifier. So, for example, an event declared to be public would allow other classes the use of += and -= on the event, but firing the event (i.e. invoking the delegate) is only allowed in the class containing the event. Let’s see an example,

    //Define publisher class as Pub
    public class Pub
    {
        //OnChange property containing all the 
        //list of subscribers callback methods
        public event Action OnChange = delegate { };
    
        public void Raise()
        {
            //Invoke OnChange Action
            OnChange();
        }
    }


    A method in another class can then subscribe to the event by adding one of its methods to the event delegate:

    Below example shows how a class can expose a public delegate and raise it.

    class Program
    {
        static void Main(string[] args)
        {
            //Initialize pub class object
            Pub p = new Pub();
            //register for OnChange event - Subscriber 1
            p.OnChange += () => Console.WriteLine("Subscriber 1!");
            //register for OnChange event - Subscriber 2
            p.OnChange += () => Console.WriteLine("Subscriber 2!");
            //raise the event
            p.Raise();
            //After this Raise() method is called
            //all subscribers callback methods will get invoked
            Console.WriteLine("Press enter to terminate!");
            Console.ReadLine();
        }
    }


    Even though the event is declared public, it cannot be directly fired anywhere except in the class containing it.

    By using event keyword compiler protects our field from unwanted access.

    And,

    It does not permit the use of = (direct assignment of a delegate). Hence, your code is now safe from the risk of removing previous subscribers by using = instead of +=.

    Also, you may have noticed special syntax of initializing the OnChange field to an empty delegate like this delegate { }. This ensures that our OnChange field will never be null. Hence, we can remove the null check before raising the event, if no other members of the class making it null.

    When you run the above program, your code creates a new instance of Pub, subscribes to the event with two different methods and raises the event by calling p.Raise. The Pub class is completely unaware of any subscribers. It just raises the event.

    In C#, delegates form the basic building blocks for events. This post explains the implementation detail of Delegates and Events in C# .NET: https://kudchikarsk.com/delegates-and-events-in-csharp/


    Saturday, 22 June 2019 6:29 PM