C # event 1, multicast Delegate 2, event 3, custom events in the previous chapter, all delegates supported only a single callback. However, a delegate variable can refer to a series of delegates, in which each delegate sequentially points to a subsequent delegate, forming a chain of delegates, or multicast delegate *multicast delegate. Using a multicast delegate, you can invoke a method chain through a method object, create a variable to reference the method chain, and pass those data types as parameters to the method. In C #, the implementation of a multicast delegate is a common pattern to avoid a lot of manual coding. This pattern, called observer (Observer) or Publish-subscribe mode, is a case in which you need to broadcast a notification of a single event (such as a change in the state of an object) to multiple Subscribers (subscriber). One, use multicast delegate to encode the observer mode to consider a temperature control example. Hypothesis: a heater and a cooler are connected to the same thermostat. To control the opening and closing of heaters and coolers, notify them of changes in temperature. The thermostat publishes changes in temperature to multiple subscribers---that is, heaters and coolers.
1 class Program 2 {3 static void Main (string[] args) 4 {5//Connection Publisher and Subscriber 6 Thermostat TM = new thermostat (); 7 Cooler cl = new Cooler (40); 8 Heater HT = new heater (60); 9//Set the method of the delegate variable Association. + = can store multiple methods, called Subscribers. Ten TM. Ontemperaturechange + = cl. ontemperaturechanged; One TM. Ontemperaturechange + + HT. ontemperaturechanged; String temperature = Console.ReadLine (); 13 14//Publish data to subscribers (essentially, run those methods sequentially). Ontemperaturechange (float. Parse (temperature)); Console.ReadLine (); 18 19 20 21} 22} 23//Two Subscribers class Cooler Cooler (float Temperatu RE) {_temperature = temperature;} The private float _temperature; public float temperature (+) {_temperature = Val Ue 36 } Notoginseng get (40} 41} 4 2 43//The future will be used as a delegate variable, also known as the Subscriber method, the public void ontemperaturechanged (float newtemperature) 45 {46 if (Newtemperature > _temperature) Console.WriteLine ("Cooler:on!"); 49 } (Console.WriteLine) ("Cooler:off!"); 53} 59} (Heater) (heater) (float temperature) {60 _temperature = temperature; The _temperature of the private float; The public float temperature (+) Set (_temperature = val) Ue (): 72} 73} ontemperaturechanged public void (float Newtemperature) (Newtemperature < _temperature), {Console.Write Line ("Heater:on!"); ("Heater:off!"); 83 } 84} 85} 86 87 88//Publisher thermostat Class 90 {91 92//define a delegate type Blic delegate void Temperaturechangehanlder (float newtemperature); 94//Define a delegate type variable to store the Subscriber list. Note: You can store all subscribers with just one delegate field. _ontemperaturechange private Temperaturechangehanlder; 96//Current temperature of the private float _currenttemperature; 98 public Temperaturechangehanlder OnTemperatureChange100 {101 set {_ontemperaturechange = value; }102 get {return _ontemperaturechange;} 103}104 106 Public float CurrentTemperature107 {108 get {return _currenttemper Ature;} 109 SET110 {111 if (value = _currenttemperature) 113 {_currenttemperature = value;114 }115}116}117}
The code above uses the + = operator to assign values directly. Two subscribers were registered to their Ontemperaturechange delegate. There is currently no value to publish the Currenttemperature property of the thermostat class each time it changes, by invoking the delegate to notify the subscriber of the change in temperature, which requires modifying the property's Set statement. After that, two subscribers will be notified of each temperature change.
Public float currenttemperature { get {return _currenttemperature;} Set { if (value! = _currenttemperature) { _currenttemperature = value; Ontemperaturechange (value);}}}
Here, you can send notifications to multiple subscribers with just one call----This is the reason why the delegate is more specifically called a multicast delegate. There are a few points to note about this: 1. A very important step in releasing the event code: If no Subscribers are currently registered to receive notifications. The Ontemperaturechange is empty and the Execute Ontemperaturechange (value) statement throws a NullReferenceException. Therefore, you need to check for null values.
Public float currenttemperature { get {return _currenttemperature;} Set { if (value! = _currenttemperature) { _currenttemperature = value; Temperaturechangehanlder localonchange = Ontemperaturechange; if (localonchange! = null) { //ontemperaturechange = null; Localonchange (value);}}}
Here, instead of checking the null value at the beginning, we first assign the Ontemperaturechange to another delegate variable Localonchange. This simple modification ensures that between checking for null values and sending notifications, If all Ontemperaturechange subscribers are removed (by a different thread), then the NullReferenceException exception is not triggered. Note: Applying the-= operator to a delegate returns a new instance. Any call to a delegate ontemperaturechange-= a subscriber does not remove a delegate from Ontemperaturechange and makes its delegate less than the previous one, but instead assigns a completely new multicast delegate to it. This will not have any effect on the original multicast delegate (Localonchange also points to the original multicast delegate), and only a reference to it will be reduced. A delegate is a reference type. 2. Delegate operators to merge the two subscribers in the thermostat example, use the "+ =" operator. This takes a delegate and adds the second delegate to the delegate chain so that a delegate points to the next delegate. After the method of the first delegate is called, it invokes the second delegate. To remove a delegate from the delegate chain, use the "-=" operator.
1 thermostat.temperaturechangehanlder delegate1;2 thermostat.temperaturechangehanlder delegate2;3 Thermostat.temperaturechangehanlder delegate3;4 delegate3 = TM. ontemperaturechange;5 delegate1 = cl. Ontemperaturechanged;6 Delegate2 = ht. Ontemperaturechanged;7 Delegate3 + = delegate1;8 delegate3 + = Delegate2;
In the same vein, you can use + and-.
1 thermostat.temperaturechangehanlder delegate1;2 thermostat.temperaturechangehanlder delegate2;3 Thermostat.temperaturechangehanlder delegate3;4 delegate1 = cl. Ontemperaturechanged;5 Delegate2 = ht. Ontemperaturechanged;6 delegate3 = delegate1 + delegate2;7 delegate3 = Delegate3-delegate2;8 TM. Ontemperaturechange = Delegate3;
Use the assignment operator, which clears all previous subscribers and allows you to replace them with new subscribers. This is a setup where delegates can easily make mistakes. Because it is necessary to use the "+ =" operation, it is easy to mistakenly write "=" Whether it is + 、-、 + =,-=, in the internal use of static methods System.Delegate.Combine () and System.Delegate.Remove () to achieve. 3, sequential call delegate call sequence diagram, need to download. Although a TM. Ontemperaturechange () calls cause each Subscriber to receive notifications, but they are still called sequentially, not concurrently, because one delegate can point to another delegate, which can point to other delegates. NOTE: The internal mechanism of a multicast delegate the delegate keyword is an alias of a type derived from System.MulticastDelegate. System.MulticastDelegate is derived from System.Delegate, which consists of an object reference and a System.Reflection.MethodInfo type of the PIN. When a delegate is created, the compiler automatically uses the System.MulticastDelegate type instead of the System.Delegate type. The MulticastDelegate class contains an object reference and a method pointer, which is the same as its delegate base class, but in addition it contains a reference to another System.MulticastDelegate object. When you add a method to a multicast delegate, the MulticastDelegate class creates a new instance of the delegate type, stores the object reference and method pointers in the new instance for the newly added method, and adds a new delegate instance as the next item in the delegate instance list. As a result, the MulticastDelegate class maintains a linked list that is composed of multiple delegate objects. When a multicast delegate is invoked, the delegate instances in the linked list are called sequentially. Typically, delegates are called in the order in which they are added. &NBSP;4, error handling error handling highlights the importance of sequential notifications. If a subscriber throws an exception, subsequent subscriptions in the chain do not receive notifications. To avoid this problem, so that all subscribers can be notified, you must manually traverse the list of subscribers and invoke them separately.
1 public Float Currenttemperature 2 {3 get {return _currenttemperature;} 4 Set 5 {6 if (value! = _currenttemperature) 7 {8 9 _curren Ttemperature = value;10 Temperaturechangehanlder Localonchange = ontemperaturechange;11 if (localonchange! = null) (Temperaturechangehanlder HANLD ER in Localonchange.getinvocationlist ())-{TRY16 {Hanlder (value); 18}19 catch (Exception e) {Console.WriteLine (e.message); 22 23}24}25}26 27}28 }29}
5, method return value and reference in this case, it is also necessary to traverse the delegate invocation list, rather than directly activate a notification. Because different subscribers may return values that differ. So it needs to be acquired separately. Ii. There are two key issues with the delegation of events currently in use. C # addresses these issues by using the Keyword event (event). Two, 1 the role of the event: 1, Package subscription as mentioned earlier, you can use the assignment operator to assign one delegate to another. However, this may cause a bug. The "=" is used where "+ =" should be used. To prevent this error, the assignment operator is not run at all for objects outside the enclosing class. The purpose of the event keyword is to provide additional encapsulation to avoid accidentally cancelling other subscribers. 2. The second important difference between encapsulating a release delegate and an event is that the event ensures that only the containment class can trigger an event notification. Prevents calling publishers outside the containment class to publish event notifications. Prohibit code such as the following: TM. Ontemperaturechange (100), TM can be called even if the TM's currenttemperature has not changed. Ontemperaturechange delegate. So, like subscribers, the problem with delegation is that the encapsulation is inadequate. II. Declaration of the Event 2 C # resolves both of these issues with the event keyword, although it looks like a field modifier, but the event defines a new member type.
1 public class thermostat 2 {3 private float _currenttemperature, 4 public float Currenttem Perature 5 {6 set {_currenttemperature = value;} 7 get {return _currenttemperature;} 8} 9//define delegate type delegate void Temperaturechangehandler (object sender, Temperatureargs new Temperatrue); 11 12//define a delegate variable and decorate it with event, and be decorated with a new name, event Publisher. Public event Temperaturechangehandler Ontemperaturechange = delegate {};14) public class temper ATUREARGS:SYSTEM.EVENTARGS17 {Private float _newtemperature;19 public float newtemper Ature20 {set {_newtemperature = value;} get {return _newtemperature;} }24 public Temperatureargs (float newtemperature)-_newtemperat ure = newtemperature;27}28}30}
This new thermostat class has been modified in several places: A, the Ontemperaturechange property is removed, and is declared as a public field B, while Ontemperaturechange is declared as a field. The event keyword is used, which disables the use of assignment operators for a public delegate field. Only the containment class can invoke a delegate that publishes notifications to all subscribers. The above two points solves the two common problems of delegate C, the other disadvantage of ordinary delegate is that it is easy to forget to check the null value before invoking the delegate, the encapsulation provided by the event keyword can take an alternative in declaring (or in the constructor), the above code assigns a null delegate. Of course, a null value check is still required if the delegate has any possibility of being re-assigned to NULL. D, the delegate type has changed, the original single temperature parameter is replaced with two new parameters. 3 Coding specifications in the above code, another modification has occurred in the delegate declaration. In order to follow the standard C # Coding specification, Temperaturechangehandler was modified to replace the original single temperature parameter with two new parameters, sender and Temperatureargs. This modification is not mandatory by the C # compiler. However, when declaring a delegate intended to be used as an event, the specification requires that you pass two parameters of these types. The first parameter, sender, contains an instance of the class that called the delegate. This parameter is especially useful if a Subscriber method registers multiple events. If two different Thermostata instances subscribe to the Heater.ontemperaturechanged event, in which case any one of the thermostat instances may trigger a call to heater.ontemperaturechanged, in order to determine the specific Which thermostat instance triggers the event to be judged internally using the sender parameter within heater.ontemperaturechanged (). The second parameter temperatureargs the Thermostat.temperatureargs type of the property. It is appropriate to use a nested class here, because it follows the same scope as the Ontermperaturechangehandler delegate itself. Thermostat.temperatureargs, one of the main points is that it derives from System.EventArgs. The only important property of System.EventArgs is empty, which indicates that no event data exists. ThenAnd, when you derive Temperatureargs from System.EventArgs, you add an extra property named Newtemperature. This allows the temperature to be passed from the thermostat to the Subscriber. Code Summary: 1. The first parameter, sender, is of type object, which contains a reference to the object that invokes the delegate. 2. The second parameter is of type System.EventArgs (or derived from System.EventArgs, but contains other types of event data. The delegate is invoked almost exactly the same way as before, just to provide additional parameters.
1 class Program 2 {3 static void Main (string[] args) 4 {5 Thermostat TM = new Thermostat (); 6 7 Cooler cl = new Cooler (40); 8 Heater HT = new heater (60); 9 10//Set Subscriber (method) one TM. Ontemperaturechange + = cl. ontemperaturechanged; TM. Ontemperaturechange + + HT. ontemperaturechanged; TM. currenttemperature = 100; 15} 16} 17//Publisher class public class thermostat + private float _currenttemperature; Public float currenttemperature (value!) = _currenttemperature) (_currenttemperature = value; (Ontemperaturechange! = null) {Ontemperaturechange (this, new Temperatur Eargs (value)); 31} 32 33} 34 } + get {return _currenttemperature;} 36} 37//Define delegate type delegate public void Temperaturechangehandler (object sender, Temperatureargs newtemperatrue); 39 40//define a delegate variable and decorate it with event, and be decorated with a new name, event Publisher. The public event Temperaturechangehandler Ontemperaturechange = delegate {}; 42 43//data type to be passed to the event the public class TemperatureArgs:System.EventArgs {Priv ate float _newtemperature; Public float newtemperature ({_newtemperature = value;} 50 get {return _newtemperature;} n Temperatureargs (float newtemperature) 53 {_newtemperature = newtemperature; 55} 56 57} 58} 59 60 Two Subscribers class Cooler (Cooler) (float temperature) + _temperature = temperature; 66 } _temperature float; The public float temperature ({) Set, _temperature = Val Ue The "_temperature" (77} 78} 79 80//future will be used as a delegate variable, also known as subscriber Method Bayi public void Ontemperaturechanged (object sender, Thermostat.temperaturea RGS newtemperature) (Newtemperature.newtemperature > _temperature) 84 {85 Console.WriteLine ("Cooler:on!"); "Console.WriteLine" ("Cooler:off!"); 90 } Heater} 94 {heater (float temperature) 96 {97 _temperature = temperature; 98} _temperature;100 public float Temperature101 {102 set103{104 _temperature = value;105}106 get107 {108 return _temperature;109}110}111 public void ontemperaturechanged (object sender, Thermostat.temperat Ureargs newtemperature) _temperature {113 if (Newtemperature.newtemperature <) 114 {1 Console.WriteLine ("Heater:on!"); }117 else118 {119 Console.WriteLine ("Heater:off!"); 120}121}122}
by specifying sender as the container class (this), because it is the only class that can invoke a delegate for an event. In this example, the Subscriber can force the sender parameter to thermostat and access the current temperature in that way, or through the Temperatureargs instance. However, the current temperature on the thermostat instance may be changed by a different thread. When an event occurs due to a state change, the previous value, along with the new value, is a common programming pattern that can determine which state changes are allowed. II, 4 generics and delegates using generics, you can use the same delegate data type in multiple locations and maintain a strong type while supporting multiple different parameter types. There is no need to declare a custom delegate data type system.eventhandler<t> already included in the Framework Class library note in most scenarios where c#2.0 and later are required to use events: System.eventhandler<t> uses a constraint to restrict the derivation of T from EventArgs. Note that this is for upward compatibility. //define delegate types public delegate void Temperaturechangehandler (object Sende R, Temperatureargs newtemperatrue); //define a delegate variable and decorate it with event, be decorated with a new name, event Publisher. public event Temperaturechangehandler Ontemperaturechange = delegate {}; Use the following generics instead: &NBSP ; public event eventhandler<temperatureargs> Ontemperaturechange = delegate {}; Internal mechanism of the event: the event is to restrict the external class to add a subscription method to the publication only through the "+ =" operator, and to unsubscribe with the "-=" operator, which is not allowed in any event. In addition, they also block any class calls other than the containment classThing For this purpose, the C # compiler obtains the public delegate variable with the event modifier and declares the delegate as private. In addition, it adds two methods and two special event blocks. Essentially, the event keyword is a C # shortcut that the compiler uses to generate the appropriate encapsulation logic. c# when a property is actually present, a get set is created, where the event properties use the Add Remove, respectively, using the Sytem.Delegate.Combine and system.delegate.remove
1//define delegate type 2 public delegate void Temperaturechangehandler (object sender, Temperatureargs newtemperatrue) ; 3 4//define a delegate variable and decorate it with event, and be decorated with a new name, event Publisher. 5 public event Temperaturechangehandler Ontemperaturechange = delegate {}; 6 7 under the function of the compiler, will automatically expand to: 8 private Temperaturechangehandler _ontemperaturechange = delegate {}; 9 public void Add_ontemperaturechange (Temperaturechangehandler handler) one {delegate.com Bine (_ontemperaturechange, handler);}14 public void Remove_ontemperaturechange (Temperaturechangehandler Handler) {Delegate.remove (_ontemperaturechange, handler);}18 public event Tem Peraturechangehandler OnTemperatureChange19 {add21 {Add_ontemperatur Echange (value);}24 remove26 {remove_ontemperaturechange (valu e); 28}2930}
The two methods Add_ontemperaturechange and Remove_ontemperaturechange are respectively responsible for implementing the "+ =" and "-=" assignment operators. In the final CIL code, the event keyword is still preserved. In other words, an event is something that the CIL code can explicitly identify, not just a C # construct. Ii. 5 Custom event implementations the code generated by the compiler for "+ =" and "-=" can be customized. For example, change the scope of the Ontemperaturechange delegate to protected instead of private. Thus, a class derived from thermostat is allowed to access the delegate directly without the same restrictions as the outer class. To do this, you can allow the addition of custom add and remove blocks.
1 protected Temperaturechangehandler _ontemperaturechange = delegate {}; 2 3 public Event Temperaturechangehandler Ontemperaturechange 4 {5 Add 6 {7 //Here code can customize the 8 Delegate.combine (_ Ontemperaturechange, value); 9 }11 remove13 {+/ /Here code can be customized delegate.remove (_ontemperaturechange , value); }17
After inheriting the subclass of this class, you can override this property. Implement custom events. Summary: Typically, a method pointer is the only thing that needs to be outside the event context or even a delegate variable. In other words: Because events provide additional encapsulation features and allow you to customize the implementation if necessary, it is a best practice to always use events for the Observer pattern.
C # Events