简体   繁体   中英

Understanding the event usage wrt delegates and registration methods

I am trying to understand the delegates and events, so far I know the concepts.

I have a question in mind and want to know if I am right.

There is a class Car. We create a public delegate (CarHandler), then we create a private member of delegate type (ListofMethods), then we create method for registering methods with this member (RegistorMethods) in this we say ListofMethods+=incoming parameter.

Then, in main program we create methods with signature same as that of the delegate(signature is return type void and parameter is string). Then, we create object of Car class. Then we register the method/methods with the delegate (method is console.writeline(incomming parameter)). Then when we invoke this class. Now, based on where in the class the ListofMethods is invoked (example:ListofMethods("Hey There");), accordingly the RegistoredMethods will fire.

So the advantage of using events instead of above example is that : I know that we can create multiple events of the same delegate type with out creating more registration methods.

Case 1 is using only delegates and no events. And case 2 is using events. Then, In case 1, all the registered methods would get the same text as invoked by ListofHandler. To create more events (events here mean the general english meaning and not the c# events) in Case 1 we would need to create more delegate members, more methods for registering new methods with this delegate member. However, in Case of EVENTS (case 2) the different events can give their own text and the instance can then register with the event it needs and it will get it fired.

In CASE 1 we would need to create more delegate members for raising multiple events(not C# events, general English meaning), where as in case of CASE 2 (events) it is enough to create only 1 delegate member. Is that right?

Question: Is the above para correct way to implement a CASE 3, that is like case 2, but only using delegates and not events. Please can you write a note on this in your answer

If not understood then you can ask me question. Please help me clear my doubt here.

Code for CASE 1:

public class Car
    {
        // 1) Define a delegate type. 
        public delegate void CarEngineHandler(string msgForCaller);

        // 2) Define a member variable of this delegate.   
        //this can be public, and if public then we can avoid writing the below RegisterWithCarEngine method, but it is not safe
        //because user can mess the values and call custom strings, etc
        private CarEngineHandler listOfHandlers;

        // 3) Add registration function for the caller.   
        public void RegisterWithCarEngine(CarEngineHandler methodToCall)
        {
            //listOfHandlers = methodToCall;  
            listOfHandlers += methodToCall;
        }

        // Internal state data.
        public int CurrentSpeed { get; set; }
        public int MaxSpeed { get; set; }
        public string PetName { get; set; }

        // Is the car alive or dead?  
        private bool carIsDead;

        // Class constructors.  
        public Car()
        {
            MaxSpeed = 100;
        }

        public Car(string name, int maxSp, int currSp)
        {
            CurrentSpeed = currSp;
            MaxSpeed = maxSp;
            PetName = name;
        }

        // 4) Implement the Accelerate() method to invoke the delegate's  
        //    invocation list under the correct circumstances. 
        public void Accelerate(int delta)
        {
            // If this car is "dead," send dead message.   
            if (carIsDead)
            {
                if (listOfHandlers != null)
                    listOfHandlers("Sorry, this car is dead...");
            }

            else
            {
                CurrentSpeed += delta;
                // Is this car "almost dead"? 
                if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null)
                {
                    listOfHandlers("Careful buddy! Gonna blow!");
                }
                if (CurrentSpeed >= MaxSpeed)
                    carIsDead = true;
                else
                    Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***** Delegates as event enablers *****\n");
            // First, make a Car object.   
            Car c1 = new Car("SlugBug", 100, 10);
            // Now, tell the car which method to call     
            // when it wants to send us messages.     
            c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
            // Speed up (this will trigger the events).     
            Console.WriteLine("***** Speeding up *****");
            for (int i = 0; i < 6; i++)
                c1.Accelerate(20);
            Console.ReadLine();

            Car c2 = new Car("SlugBug1", 100, 10);
            // Speed up (this will trigger the events).     
            Console.WriteLine("***** Speeding up *****");
            for (int i = 0; i < 6; i++)
                c2.Accelerate(20);
            Console.ReadLine();
        }
        // This is the target for incoming events. 
        public static void OnCarEngineEvent(string msg)
        {
            Console.WriteLine("\n***** Message From Car Object *****");
            Console.WriteLine("=> {0}", msg);
            Console.WriteLine("***********************************\n");
        }
    }

Code for CASE 2:

public class Car 
{   // This delegate works in conjunction with the 
  // Car's events.   
public delegate void CarEngineHandler(string msg); 
// This car can send these events.   
public event CarEngineHandler Exploded;   
public event CarEngineHandler AboutToBlow;  
...
 }

public void Accelerate(int delta) 
{   
// If the car is dead, fire Exploded event. 
  if (carIsDead) 
  {   
  if (Exploded != null) 
      Exploded("Sorry, this car is dead...");  
 } 
  else  
 {     CurrentSpeed += delta;  
    // Almost dead?
     if (10 == MaxSpeed - CurrentSpeed && AboutToBlow != null)  
   {      
 AboutToBlow("Careful buddy!  Gonna blow!");  
   }  
    // Still OK! 
    if (CurrentSpeed >= MaxSpeed)   
    carIsDead = true;
     else      
 Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed); 
  } 
}

static void Main(string[] args)
 {   
Console.WriteLine("***** Fun with Events *****\n"); 
  Car c1 = new Car("SlugBug", 100, 10); 
// Register event handlers.  
 c1.AboutToBlow += CarIsAlmostDoomed; 
  c1.AboutToBlow += CarAboutToBlow; 
  c1.Exploded += CarExploded;  
  Console.WriteLine("***** Speeding up *****");  
 for (int i = 0; i < 6; i++)    
 c1.Accelerate(20);  
  c1.Exploded -= CarExploded;  
  Console.WriteLine("\n***** Speeding up *****"); 
  for (int i = 0; i < 6; i++)   
  c1.Accelerate(20);  
  Console.ReadLine(); 
public static void CarAboutToBlow(string msg)   { Console.WriteLine(msg); }  
  public static void CarIsAlmostDoomed(string msg)   { Console.WriteLine("=> Critical Message from Car: {0}", msg); }  
  public static void CarExploded(string msg)   { Console.WriteLine(msg); } 
} 

The difference between your two cases basically boil down to this difference:

Case 1

public class Car
{
    void RegisterWithCarEngine(CarEngineHandler methodToCall);
}

Case 2

public class Car
{
    event CarEngineHandler Exploded;   
    event CarEngineHandler AboutToBlow;
}

Case 1 is rather odd. There is nothing there to let a consumer of this class know what this method does - or when it will fire. Also, and perhaps more importantly, there is no way to detach then event handler.

Case 2 is more standard. It fits with the concept of giving a good naming convention and it is clear that these two members are events. It is therefore obvious to a consumer that they can attach and detach to these events.

You need to think about it a bit like if this where your design:

public class Car
{
    void SetSpeed(string speedName, int speed);
    int GetSpeed(string speedName);
}

I might then code it like this:

car.SetSpeed("Max", 50);
car.SetSpeed("Current", 10);
Console.WriteLine(car.GetSpeed("Max"));
Console.WriteLine(car.GetSpeed("Current"));

Now while this provide nominally the same functionality as your class - and same may argue that it offers even more functionality - it hides the functionality as seen by a consumer of the class.

It is far better to go with the interface provided by Case 2.

Just as a side note, you should always call your event code like this:

var x = Exploded;
if (x != null)
    x("Sorry, this car is dead...");

It is possible that the delegates on Exploded can be removed between the null check and the call. The temporary assignment prevents that issue.

Your two cases are nearly identical. The only material difference is that when you use an event in your class (ie "case 2"), and you don't implement it explicitly, the compiler automatically generates the field that you would have had to declare in "case 1", as well as the method to allow subscription/registration.

Something that often surprises people, even occasionally those who have been using C# for some time, is a statement like mine above:

and you don't implement it explicitly

What's that statement mean? Many people don't realize that, as with a property, it is possible to either let the compiler implement the member, or to do it yourself.

In the case of the property, you implement a get and/or a set method. In the case of an event, the methods are named add and remove . And of course, if you implement it yourself, you need to also provide the backing field or other mechanism to track subscribers (just like in a property).


So, what's this all mean in your specific example? Well, to me it means that if you have event-like semantics, then you definitely should just go ahead and implement that as an actual event member. The code will all basically compile down to equivalent IL regardless of which way you do it, but using an event takes advantage of the language's high-level abstraction. This makes the code easier both to read and write, and so makes it more maintainable and less likely to contain bugs.

You can keep in mind the approach in "case 1", in case you wind up in a situation where declaring an event doesn't work (eg some kind of interop with a platform or API that doesn't deal with or support the .NET event paradigm). But in most situations, event is the way to go.


It seems that part of your concern is the question of the delegate members (ie the declared delegate types). Frankly, you have this issue regardless of which way you approach the problem. If you have a way of reusing a single delegate type for multiple event members in a class, then you can also reuse that single delegate type for the explicit field-and-registration-method approach ("case 1").

In most cases, you should not be declaring your own delegate type anyway. Just use eg EventHandler<T> , or one of the general purpose Action or Func types.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM