Introduction
Delegation and events are widely used in. NET Framework. However, it is not easy for many people who have been in contact with C # for a long time to better understand delegation and events. They are like a hacker. People who have passed the hacker think it is too easy, and people who have never seen the delegate and event will feel different (BI) panic and uncomfortable. In this article, I will use two examples to explain from a simple perspective what is delegation, why is delegation used, how events come from, and why ,. the significance of delegation, events, delegation, and events in the Net Framework to the observer design pattern, and their intermediate code is also discussed.
Use a method as a method parameter
Let's look at the two simplest methods, no matter how the title is spoken or whether the delegate is actually something. They are just a greeting on the screen:
Public void greetpeople (string name ){
// Do some additional things, such as initialization.
Englishgreeting (name );
}
Public void englishgreeting (string name ){
Console. writeline ("Morning," + name );
}
For the moment, no matter whether the two methods have any practical significance. Greetpeople is used to say hello to someone. When we pass the name parameter representing someone's name, such as "Jimmy", In this method, the englishgreeting method will be called and the name parameter will be passed again, englishgreeting is used to output "Morning, Jimmy" to the screen ".
Now let's assume that this program needs to be global. Oh, no. I am a Chinese. I don't understand what "Morning" means. What should I do? Okay, let's add a Chinese edition greeting method:
Public void chinesegreeting (string name ){
Console. writeline ("good morning," + name );
}
At this time, greetpeople also needs to be changed. Otherwise, how can we determine which version of greeting method is suitable? Before proceeding, we 'd better define another enumeration as the basis for judgment:
Public Enum language {
English, Chinese
}
Public void greetpeople (string name, language Lang ){
// Do some additional things, such as initialization.
Swith (Lang ){
Case language. English:
Englishgreeting (name );
Break;
Case language. Chinese:
Chinesegreeting (name );
Break;
}
}
Okay, even though this solves the problem, it is easy to think of without saying that this solution has poor scalability. If we need to add Korean and Japanese Versions later, you have to modify the enumeration and greetpeople () methods repeatedly to meet new requirements.
Before considering a new solution, let's look at the greetpeople method signature:
Public void greetpeople (string name, language Lang)
Let's just look at the string name. Here, string is a parameter type, and name is a parameter variable. When we assign the name string "Jimmy", it represents the value of "Jimmy; when we grant it "Zhang Ziyang", it also represents the value of "Zhang Ziyang. Then, we can perform other operations on this name in the method body. Ah, is this nonsense? I knew it when I first learned the program.
If you think about it again, if the greetpeople () method can accept a parameter variable, this variable can represent another method. When we assign englishgreeting to this variable, it represents englsihgreeting () this method. When we assign chinesegreeting to it, it also represents the chinesegreeting () method. We name this parameter variable makegreeting, so it is not the same as when we assign a value to the name. When we call the greetpeople () method, is this makegreeting parameter also assigned a value (chinesegreeting or englsihgreeting )? Then, we can use makegreeting in the method body just like using other parameters. However, because makegreeting represents a method, it should be used in the same way as the method it is assigned (such as chinesegreeting), for example:
Makegreeting (name );
Now that we have the idea, let's change the greetpeople () method. It should look like this:
Public void greetpeople (string name, *** makegreeting ){
Makegreeting (name );
}
Note that ***, this location should usually be placed in the parameter type, but so far, we just thought that there should be a parameter that can represent the method, and rewrite the greetpeople method according to this idea. Now there is a big problem: this represents the type of the makegreeting parameter of the method?
Note: you no longer need to enumerate here, because when assigning values to makegreeting, you dynamically decide which method to use, whether it is chinesegreeting or englishgreeting. Inside these two methods, we have already made a distinction between "Morning" and "good morning.
You should have thought about it. Now it's time to delegate the game, but let's look at the chinesegreeting () and englishgreeting () parameters that can be represented by the delegate () method signature:
Public void englishgreeting (string name)
Public void chinesegreeting (string name)
As if the name can accept "true" and "1" of the string type, but cannot accept the same true of the bool type and 1 of the int type. The parameter Type Definition of makegreeting should be able to determine the types of methods that can be represented by makegreeting. Further, it is the parameter type of the methods that makegreeting can represent and the question of "reeting? Then, the delegate displays: it defines the type of the method that the makegreeting parameter can represent, that is, the type of the makegreeting parameter.
Note: If the above sentence is a detour, I will translate it into this: String defines the type of the value that the name parameter can represent, that is, the type of the name parameter.
Definition of delegation in this example:
Public Delegate void greetingdelegate (string name );
Can I compare it with the signature of the above englishgreeting () method? Except for the delegate keyword, are the rest identical?
Now let's change the greetpeople () method again, as shown below:
Public void greetpeople (string name, greetingdelegate makegreeting ){
Makegreeting (name );
}
As you can see, the position of the delegate greetingdelegate is the same as that of the string, and the string is a type, so greetingdelegate should also be a type or class ). However, the delegate declaration method is completely different from the class. What is this? In fact, the Delegate will indeed be compiled into classes during compilation. Because delegate is a class, delegates can be declared wherever classes can be declared. For more information, see the complete code of this example:
Using system;
Using system. Collections. Generic;
Using system. text;
Namespace delegate {
// Define the delegate, which defines the types of methods that can be represented
Public Delegate void greetingdelegate (string name );
Class program {
Private Static void englishgreeting (string name ){
Console. writeline ("Morning," + name );
}
Private Static void chinesegreeting (string name ){
Console. writeline ("good morning," + name );
}
// Note that this method accepts a greetingdelegate method as a parameter.
Private Static void greetpeople (string name, greetingdelegate makegreeting ){
Makegreeting (name );
}
Static void main (string [] ARGs ){
Greetpeople ("Jimmy Zhang", englishgreeting );
Greetpeople ("Zhang Ziyang", chinesegreeting );
Console. readkey ();
}
}
}
The output is as follows:
Morning, Jimmy Zhang
Good morning, Zhang Ziyang
We now make a summary of the delegation:
A delegate is a class that defines the type of a method so that the method can be passed as a parameter of another method. This way, the method is dynamically assigned to the parameter, it can avoid using the IF-else (switch) statement in a large number in the program, and make the program more scalable.
Bind method to delegate
Do you feel like waking up? So, are you wondering: In the above example, I don't have to assign a value to the name parameter directly in the greetpeople () method. I can use the variable like this:
Static void main (string [] ARGs ){
String name1, name2;
Name1 = "Jimmy Zhang ";
Name2 = "Zhang Ziyang ";
Greetpeople (name1, englishgreeting );
Greetpeople (name2, chinesegreeting );
Console. readkey ();
}
Since the same status of the delegate greetingdelegate and the type string is defined as a parameter type, can I use the delegate as well?
Static void main (string [] ARGs ){
Greetingdelegate delegate1, delegate2;
Delegate1 = englishgreeting;
Delegate2 = chinesegreeting;
Greetpeople ("Jimmy Zhang", delegate1 );
Greetpeople ("Zhang Ziyang", delegate2 );
Console. readkey ();
}
As expected, this is fine, and the program is output as expected. Here, I want to talk about the Delegate Feature different from the string feature: You can assign multiple methods to the same delegate, or bind multiple methods to the same delegate, when this delegate is called, the bound methods are called in sequence. In this example, the syntax is as follows:
Static void main (string [] ARGs ){
Greetingdelegate delegate1;
Delegate1 = englishgreeting; // assign a value to the delegate Type Variable
Delegate1 + = chinesegreeting; // bind the delegate variable to another method.
// The englishgreeting and chinesegreeting methods will be called successively
Greetpeople ("Jimmy Zhang", delegate1 );
Console. readkey ();
}
Output:
Morning, Jimmy Zhang
Good morning, Jimmy Zhang
In fact, we can also bypass the greetpeople method and directly call englishgreeting and chinesegreeting through delegation:
Static void main (string [] ARGs ){
Greetingdelegate delegate1;
Delegate1 = englishgreeting; // assign a value to the delegate Type Variable
Delegate1 + = chinesegreeting; // bind the delegate variable to another method.
// The englishgreeting and chinesegreeting methods will be called successively
Delegate1 ("Jimmy Zhang ");
Console. readkey ();
}
Note: There is no problem in this example, but looking back at the above greetpeople () definition, we can do some work that needs to be done for both engshihgreeting and chinesegreeting, I omitted it for convenience.
Note that "=" is used for the first time, which is the syntax of value assignment; "+ =" is used for the second time, which is the binding syntax. If "+ =" is used for the first time, the compilation error "using unassigned local variables" will occur.
We can also use the following code to simplify this process:
Greetingdelegate delegate1 = new greetingdelegate (englishgreeting );
Delegate1 + = chinesegreeting; // bind the delegate variable to another method.
As you can see, it should be noted that the first statement of this Code is similar to instantiating a class. You can't help but think that the "+ =" Compilation error cannot be used when the delegate is bound for the first time, this method may be used to avoid:
Greetingdelegate delegate1 = new greetingdelegate ();
Delegate1 + = englishgreeting; // The binding syntax is "+ =.
Delegate1 + = chinesegreeting; // bind the delegate variable to another method.
But in fact, there will be a compilation error: the "greetingdelegate" method does not use the "0" parameter overload. Although this result is a bit frustrating, the compilation prompt "no overload of 0 Parameters" reminds us of the class constructor again. I know that you may not be able to find out what it is, but before that, we need to finish introducing basic knowledge and applications.
Since a delegate can bind a method, there should be a way to unbind the method. It is easy to think that this syntax is "-= ":
Static void main (string [] ARGs ){
Greetingdelegate delegate1 = new greetingdelegate (englishgreeting );
Delegate1 + = chinesegreeting; // bind the delegate variable to another method.
// The englishgreeting and chinesegreeting methods will be called successively
Greetpeople ("Jimmy Zhang", delegate1 );
Console. writeline ();
Delegate1-= englishgreeting; // cancel binding to the englishgreeting Method
// Only chinesegreeting will be called
Greetpeople ("Zhang Ziyang", delegate1 );
Console. readkey ();
}
Output:
Morning, Jimmy Zhang
Good morning, Jimmy Zhang
Good morning, Zhang Ziyang
Let's summarize the delegation again:
You can use a delegate to bind multiple methods to the same delegate variable. When you call this variable (the word "call" is used here because this variable represents a method ), you can call all bound methods in sequence.
Event Origin
Let's continue to think about the above program: The above three methods are defined in the programe class, so as to facilitate understanding. In actual application, greetpeople is usually in a class, chinesegreeting and englishgreeting are in another class. Now you have a preliminary understanding of the delegation. It is time to improve the above example. Suppose we put greetingpeople () in a class called greetingmanager, then the new program should look like this:
Namespace delegate {
// Define the delegate, which defines the types of methods that can be represented
Public Delegate void greetingdelegate (string name );
// New greetingmanager class
Public class greetingmanager {
Public void greetpeople (string name, greetingdelegate makegreeting ){
Makegreeting (name );
}
}
Class program {
Private Static void englishgreeting (string name ){
Console. writeline ("Morning," + name );
}
Private Static void chinesegreeting (string name ){
Console. writeline ("good morning," + name );
}
Static void main (string [] ARGs ){
//......
}
}
}
At this time, if you want to implement the output shown above, the main method should be like this:
Static void main (string [] ARGs ){
Greetingmanager GM = new greetingmanager ();
GM. greetpeople ("Jimmy Zhang", englishgreeting );
GM. greetpeople ("Zhang Ziyang", chinesegreeting );
}
We run this code. Well, there is no problem. The program output as expected:
Morning, Jimmy Zhang
Good morning, Zhang Ziyang
Now, let's assume that we need to use the knowledge learned in the previous section to bind multiple methods to the same delegate variable. What should we do? Let's rewrite the code again:
Static void main (string [] ARGs ){
Greetingmanager GM = new greetingmanager ();
Greetingdelegate delegate1;
Delegate1 = englishgreeting;
Delegate1 + = chinesegreeting;
GM. greetpeople ("Jimmy Zhang", delegate1 );
}
Output:
Morning, Jimmy Zhang
Good morning, Jimmy Zhang
Here, we can't help but think of object-oriented design, which focuses on Object encapsulation. Since we can declare variables of the delegate type (in the above example, It is delegate1 ), why don't we encapsulate this variable in the greetmanager class? Isn't it more convenient to use in the client of this class? Therefore, we rewrite the greetmanager class as follows:
Public class greetingmanager {
// Declare the delegate1 variable in the greetingmanager class
Public greetingdelegate delegate1;
Public void greetpeople (string name, greetingdelegate makegreeting ){
Makegreeting (name );
}
}
Now, we can use this delegate variable as follows:
Static void main (string [] ARGs ){
Greetingmanager GM = new greetingmanager ();
GM. delegate1 = englishgreeting;
GM. delegate1 + = chinesegreeting;
GM. greetpeople ("Jimmy Zhang", GM. delegate1 );
}
Although this has achieved our desired results, it does not seem very elegant. Just register the first method with "=", and the second method with "+ =" makes people feel awkward. Now it's the turn of event. in C #, events can be used to accomplish this task. We rewrite the greetingmanager class, which is like this:
Public class greetingmanager {
// This time we declare an event
Public event greetingdelegate makegreet;
Public void greetpeople (string name, greetingdelegate makegreeting ){
Makegreeting (name );
}
}
It is easy to note that the only difference between the makegreet event Declaration and the previous delegate variable delegate1 Declaration is that an event keyword is added. As you can see, the event is not easy to understand. Declaring an event is just like declaring a variable of the delegate type.
We take it for granted to rewrite the main method:
Static void main (string [] ARGs ){
Greetingmanager GM = new greetingmanager ();
GM. makegreet = englishgreeting; // compilation Error 1
GM. makegreet + = chinesegreeting;
GM. greetpeople ("Jimmy Zhang", GM. makegreet); // compilation Error 2
}
This time, you will get a compilation error: the event "delegate. greetingmanager. makegreet" can only appear on the left side of the event + = or-= (except when it is used in the type "delegate. greetingmanager ).
Event and delegate compilation code
At this time, we have to comment out the lines with compilation errors, re-compile them, and use the reconfigurator to explore the event declaration statement to see why such errors occur:
Public event greetingdelegate makegreet;
As you can see, although we declared makegreet as public in greetingmanager, in fact, makegreet will be compiled into private fields. No wonder the above compilation error will occur, because it does not allow access outside the greetingmanager class by assignment.
Let's take a further look at the code generated by makegreet:
Private greetingdelegate makegreet; // The Event Declaration actually declares a private delegate variable.
[Methodimpl (methodimploptions. Synchronized)]
Public void add_makegreet (greetingdelegate value ){
This. makegreet = (greetingdelegate) Delegate. Combine (this. makegreet, value );
}
[Methodimpl (methodimploptions. Synchronized)]
Public void remove_makegreet (greetingdelegate value ){
This. makegreet = (greetingdelegate) Delegate. Remove (this. makegreet, value );
}
Now it is clear that the makegreet event is indeed a greetingdelegate delegate, but whether it is declared as public or not, it is always declared as private. In addition, it has two methods: add_makegreet and remove_makegreet. These two methods are used to register the delegate type and cancel the registration respectively. In fact, "+ =" corresponds to add_makegreet, "-=" corresponds to remove_makegreet. The access restriction of these two methods depends on the access restriction character when the event is declared.
In the add_makegreet () method, the combine () static method of system. delegate is called. This method is used to add the current variable to the delegate linked list. We mentioned twice before that the delegate is actually a class. When we define the delegate:
Public Delegate void greetingdelegate (string name );
When the compiler encounters this code, it will generate the following complete class:
Public class greetingdelegate: system. multicastdelegate {
Public greetingdelegate (Object @ object, intptr method );
Public Virtual iasyncresult begininvoke (string name, asynccallback callback, object @ object );
Public Virtual void endinvoke (iasyncresult result );
Public Virtual void invoke (string name );
}
For more information about this class, see CLR via C # and other related books.
Delegation, events, and Observer Design Patterns
Example
The above example is not enough for the following explanation. Let's take a look at a new example. Because we have already introduced a lot of content, the progress in this section will be slightly faster:
If we have a high-end water heater, we can power it on. When the water temperature exceeds 95 degrees: 1. The speaker will start to send a voice to tell you the temperature of the water; 2. the LCD screen will also change the display of the water temperature to prompt that the water has been burned out.
Now we need to write a program to simulate the water burning process. We will define a class to represent the water heater. We call it heater, which has a field representing the water temperature, called temperature; of course, there is also an essential method of water supply heating, boilwater (), a method of Voice Alarm makealert (), a method of displaying water temperature, showmsg ().
Namespace delegate {
Class heater {
Private int temperature; // Water Temperature
// Boil water
Public void boilwater (){
For (INT I = 0; I <= 100; I ++ ){
Temperature = I;
If (temperature> 95 ){
Makealert (temperature );
Showmsg (temperature );
}
}
}
// Send a voice alarm
Private void makealert (int param ){
Console. writeline ("alarm: Tick, water has been {0} degrees:", Param );
}
// Display the water temperature
Private void showmsg (int param ){
Console. writeline ("display: The water is almost open. Current temperature: {0. ", Param );
}
}
Class program {
Static void main (){
Heater ht = new heater ();
Ht. boilwater ();
}
}
}
Introduction to the Observer Design Mode
The above example can obviously complete the work we described earlier, but it is not good enough. It is assumed that the water heater is composed of three parts: water heater, alarm, and display, which are from different manufacturers and assembled. Then, the water heater is only responsible for boiling water. It cannot issue an alarm or display the water temperature. When boiling water, an alarm is triggered by an alarm, a display prompt, and a water temperature.
At this time, the above example should look like this:
// Water Heater
Public class heater {
Private int temperature;
// Boil water
Private void boilwater (){
For (INT I = 0; I <= 100; I ++ ){
Temperature = I;
}
}
}
// Alarm
Public class alarm {
Private void makealert (int param ){
Console. writeline ("alarm: Tick, water has been {0} degrees:", Param );
}
}
// Display
Public class display {
Private void showmsg (int param ){
Console. writeline ("display: the water has been burned out. Current temperature: {0. ", Param );
}
}
There is a problem: how to notify the alarm and display when the water is turned on? Before proceeding, let's take a look at the observer design mode. The observer design mode mainly includes the following two types of objects:
Subject: A monitoring object, which usually contains content of interest to other objects. In this example, the water heater is a monitoring object, and the content that other objects are interested in is the temprature field. When the value of this field is approaching 100, the data is constantly sent to the object that monitors it.
Observer: The monitor that monitors subject. When something in a subject occurs, it notifies observer, and the observer takes corresponding action. In this example, the observer has an alarm and a monitor, and they take the action of issuing an alarm and displaying the water temperature respectively.
In this example, the sequence of events should be as follows:
The alarm and monitor tell the water heater that it is interested in its temperature (registered ).
The water heater retains reference to alarms and monitors after it is known.
When the water temperature exceeds 95 degrees, the water heater automatically calls the makealert () method of the alarm and the showmsg () method of the monitor by referencing the alarm and display.
There are many examples like this. gof abstracts it and is called the observer design pattern: The Observer Design Pattern aims to define a one-to-many dependency between objects, so that when the state of an object changes, other objects dependent on it will be automatically notified and updated. The observer mode is a loosely coupled design mode.
Observer Design Mode for implementing examples
We have already introduced a lot of delegation and events before, and it should be easy to write code now. Now we will give the code directly here and explain it in the annotations.
Using system;
Using system. Collections. Generic;
Using system. text;
Namespace delegate {
// Water Heater
Public class heater {
Private int temperature;
Public Delegate void boilhandler (int param); // declare the delegate
Public event boilhandler boilevent; // declare an event
// Boil water
Public void boilwater (){
For (INT I = 0; I <= 100; I ++ ){
Temperature = I;
If (temperature> 95 ){
If (boilevent! = NULL) {// if an object is registered
Boilevent (temperature); // call the methods of all registered objects
}
}
}
}
}
// Alarm
Public class alarm {
Public void makealert (int param ){
Console. writeline ("alarm: Tick, water has been {0} degrees:", Param );
}
}
// Display
Public class display {
Public static void showmsg (int param) {// static method
Console. writeline ("display: The water is almost burned out. Current temperature: {0. ", Param );
}
}
Class program {
Static void main (){
Heater heater = new heater ();
Alarm alarm = new alarm ();
Heater. boilevent + = alarm. makealert; // Registration Method
Heater. boilevent + = (new alarm (). makealert; // Method for registering anonymous objects
Heater. boilevent + = display. showmsg; // register the static method
Heater. boilwater (); // The method of boiling water, which automatically calls the method of registering an object
}
}
}
Output:
Alarm: The water is 96 degrees away:
Alarm: The water is 96 degrees away:
Display: the water is boiling. The current temperature is 96 degrees.
// Omit...
Delegate and event in. NET Framework
Although the above example completes what we want to do, we are not only confused: Why is the event model in. NET Framework different from the above? Why are there many eventargs parameters?
Before answering the above questions, we should first understand the encoding specifications of. NET Framework:
The name of the delegate type should end with eventhandler.
Prototype definition of the delegate: There is a void returned value, and two input parameters are accepted: an object type, and an eventargs type (or inherited from eventargs ).
The event is named as the part left after the eventhandler is removed by the delegate.
The types inherited from eventargs should end with eventargs.
Next, let's explain:
The parameter of the object type in the delegate declaration prototype represents the subject, that is, the monitoring object. In this example, it is the heater (water heater ). A callback function (such as makealert of alarm) can be used to access the event-triggered object (heater ).
The eventargs object contains the data that the observer is interested in. In this example, It is temperature.
In fact, the above is not only for coding specifications, but also makes the program more flexible. For example, if we not only want to get the temperature of the water heater, but also want to get its production date, model, and price in the observer side (alarm or display) method, the delegation and method declaration will become very troublesome. If we pass the water heater reference to the method of the alarm, we can directly access the water heater in the method.
Now we can rewrite the previous example to make it conform to. NET Framework specifications:
Using system;
Using system. Collections. Generic;
Using system. text;
Namespace delegate {
// Water Heater
Public class heater {
Private int temperature;
Public string type = "realfire 001"; // Add a model for demonstration
Public String area = "China Xian"; // Add the origin as a demo
// Declare the delegate
Public Delegate void boiledeventhandler (Object sender, boliedeventargs E );
Public event boiledeventhandler boiled; // declare the event
// Define the boliedeventargs class and pass the information to the observer.
Public class boliedeventargs: eventargs {
Public readonly int temperature;
Public boliedeventargs (INT temperature ){
This. temperature = temperature;
}
}
// The class inherited from heater can be rewritten so that the inherited class rejects the monitoring of other objects.
Protected virtual void onbolied (boliedeventargs e ){
If (boiled! = NULL) {// if an object is registered
Boiled (this, e); // call the method of all registered objects
}
}
// Boil water.
Public void boilwater (){
For (INT I = 0; I <= 100; I ++ ){
Temperature = I;
If (temperature> 95 ){
// Create a boliedeventargs object.
Boliedeventargs E = new boliedeventargs (temperature );
Onbolied (E); // call the onbolied Method
}
}
}
}
// Alarm
Public class alarm {
Public void makealert (Object sender, heater. boliedeventargs e ){
Heater heater = (heater) sender; // are you familiar with this?
// Access public fields in sender
Console. writeline ("alarm: {0}-{1}:", heater. Area, heater. type );
Console. writeline ("alarm: Tick, water has {0} degrees:", E. Temperature );
Console. writeline ();
}
}
// Display
Public class display {
Public static void showmsg (Object sender, heater. boliedeventargs e) {// static method
Heater heater = (heater) sender;
Console. writeline ("display: {0}-{1}:", heater. Area, heater. type );
Console. writeline ("display: The water is almost burned out. Current temperature: {0. ", E. Temperature );
Console. writeline ();
}
}
Class program {
Static void main (){
Heater heater = new heater ();
Alarm alarm = new alarm ();
Heater. Boiled + = alarm. makealert; // Registration Method
Heater. Boiled + = (new alarm (). makealert; // Method for registering anonymous objects
Heater. Boiled + = new heater. boiledeventhandler (alarm. makealert); // You can also register
Heater. Boiled + = display. showmsg; // register the static method
Heater. boilwater (); // The method of boiling water, which automatically calls the method of registering an object
}
}
}
Output:
Alarm: China Xian-realfire 001:
Alarm: The water is 96 degrees away:
Alarm: China Xian-realfire 001:
Alarm: The water is 96 degrees away:
Alarm: China Xian-realfire 001:
Alarm: The water is 96 degrees away:
Display: China Xian-realfire 001:
Display: the water is boiling. The current temperature is 96 degrees.
// Omit...
Summary
In this article, I first introduced the concept of delegation, what to do with delegation through a greetingpeople applet, and then introduced the event, next, we will give a rough description of the intermediate code generated by the delegate and event.
In the second example of a slightly complex water heater, I briefly introduced the observer design mode, completed the mode by implementing this example, and then described it.. NET Framework.