1. Proxy (delegate)
In most cases, when calling a function, we specify the function to be called directly. For example, if the class myclass has a function named process, we usually call it as follows:
Myclass = new myclass ();
Myclass. Process ();
This kind of call is feasible in most cases. However, sometimes we don't want to call a function directly, but want to pass it to others for them to call. This method is particularly useful in an event-driven system (User Interface. For example, when a user clicks a buttonCodeOr when I want to record some information but cannot specify the record method.
Consider the following example:
Public class myclass
{
Public void process ()
{
Console. writeline ("process () begin ");
// There are other things...
Console. writeline ("process () End ");
}
}
In this class, we make some records to understand the start time and end time of the function. However, our records are only sent to the console, which may not be what we need. What we really need is to be able to control the location of information recorded from outside the function, without the need to make the function code complex.
In this case, proxy is the ideal solution. The proxy allows us to specify the function to be called, as if we do not need to specify a function. The Declaration of the proxy is similar to the declaration of the function. The difference is that in this case, we declare the function signature that the proxy can reference.
In our example, a proxy with a single string parameter and no return type will be declared. Modify the class as follows:
Public class myclass
{
Public Delegate void loghandler (string message );
Public void process (loghandler)
{
If (loghandler! = NULL)
Loghandler ("process () begin ");
// There are other things
If (loghandler! = NULL)
Loghandler ("process () End ");
}
}
Using a proxy is similar to calling a function directly. Before calling a function, we only need to check whether the proxy is empty (that is, it does not point to a function ).
To call the process () function, we need to declare a record function that matches the proxy, and then create the proxy instance to point to the function. Then, pass the proxy to the process () function.
Class Test
{
Static void logger (string S)
{
Console. writeline (s );
}
Public static void main ()
{
Myclass = new myclass ();
Myclass. loghandler LH = new myclass. loghandler (logger );
Myclass. Process (LH );
}
}
The logger () function is a function that we want to call from the process () function. We declare it to match it with the proxy. In Main (), we create an instance of the proxy, and then pass the function to the proxy constructor so that it points to the function. Finally, we pass the proxy to the process () function, which then calls the logger () function.
If you are used to the C ++ language, you may think that the proxy is very similar to the function pointer. This idea is very close to the facts. However, the proxy is not just a function pointer, but also provides many other functions.
1.1 pass state)
In the preceding simple example, the logger () function only outputs some strings. A different function may record information to a file, but to perform this operation, the function needs to know the file in which the information is written.
For Win32, when you pass the function pointer, the status can be passed along with it. But for C #, this is unnecessary, because the proxy can point both to static functions and to member functions. The following is an example of how to point to a member function:
Class filelogger
{
Filestream;
Streamwriter;
Public filelogger (string filename)
{
Filestream = new filestream (filename, filemode. Create );
Streamwriter = new streamwriter (filestream );
}
Public void logger (string S)
{
Streamwriter. writeline (s );
}
Public void close ()
{
Streamwriter. Close ();
Filestream. Close ();
}
}
class test
{< br> Public static void main ()
{< br> filelogger FL = new filelogger ("process. log ");
myclass = new myclass ();
myclass. loghandler LH = new myclass. loghandler (FL. logger);
myclass. process (LH);
FL. close ();
}< BR >}
The filelogger class only encapsulates files. We modified main () so that the proxy points to the logger () function of the filelogger FL instance. When the proxy is activated from process (), the member function is called and the string is recorded in the corresponding file.
The advantage is that we don't have to change the process () function-the code is the same for the proxy, whether it is a static function or a member function.
1.2 Multicasting)
Although the functionality that points to a member function is satisfying, you can use the proxy to skillfully complete other tasks. In C #, proxies are "Multicast", which means they can point to more than one function at the same time (based on the system. multicastdelegate type ). The multicast proxy maintains a function list. When this proxy is called, all functions in the list will be called. We can add the record function in the first example and then call these two proxies. To combine the two proxies, use the delegate. Combine () function. The Code is as follows:
Myclass. loghandler LH = (myclass. loghandler)
Delegate. Combine (new delegate []
{New myclass. loghandler (logger ),
New myclass. loghandler (FL. Logger )});
Ah, it's really ugly! Fortunately, C # provides a better syntax, instead of imposing the above syntax on users. You do not need to call delegate. Combine (). Only use + = to combine the two proxies:
Myclass. loghandler LH = NULL;
LH + = new myclass. loghandler (logger );
LH + = new myclass. loghandler (FL. Logger );
This is much simpler. To delete a Proxy from the multicast proxy, you can call delegate. Remove () or use the-= Operator (I know which proxy will be used ).
When you call a multicast proxy, the proxy in the call list is synchronously called in the order of appearance. If an error occurs during this process, the execution is interrupted.
If you want to strictly control the call sequence (for example, to make a foolproof call), you can obtain the call list from the proxy and then call these functions on your own. The following is an example:
Foreach (loghandler in LH. getinvocationlist ())
{
Try
{
Loghandler (Message );
}
Catch (exception E)
{
// Handle exceptions here?
}
}
The Code only wraps each call in a try-catch pair, so that the exception thrown in a call handler will not hinder the activation of other handler calls.
2. Events)
We have discussed the proxy for a long time. Now we should talk about the event. An obvious question is: "since we already have a proxy, why do we still need an event ?"
The best way to answer this question is to consider the events that occur on the user interface objects. For example, a button may have a public "click" proxy. We can mount a function to this proxy, so that when you click this button, you can call this proxy. For example:
Button. Click = new button. clickhandler (clickfunction );
It indicates that when you click this button, clickfunction () is called ().
Quiz: Is there a problem with the above Code? What have we forgotten?
The answer is: we forgot to use + = and directly assigned a proxy. This means that any other agent that is attached to "button. Click" will be removed now. "Button. Click" should be public so that other objects can access it. Therefore, the above situation cannot be avoided. Similarly, to delete a proxy, you may write the following code:
Button. Click = NULL;
This will delete all proxies.
These situations are extremely bad, because in many cases only one proxy is mounted, and the problem is not obviously represented as a bug. Then, when another Proxy is mounted, it will be terrible!
The event adds a protection layer to the proxy model. Here is an example of an object that supports events:
Public class myobject
{
Public Delegate void clickhandler (Object sender, eventargs E );
Public event clickhandler click;
Protected void onclick ()
{
If (Click! = NULL)
Click (this, null );
}
}
The clickhandler proxy uses the standard mode of the event proxy to define the event signature. It ends with a handler name and has two parameters. The first parameter is the object that sends the event, and the second parameter is used to pass the information that is accompanied by the event. In this example, there is no information to be passed, so eventargs is used directly; however, if there is data to be passed, classes derived from eventargs (such as mouseeventargs) are used ).
The "click" event Declaration does two things: first, it declares a proxy member variable named "click", which is used inside the class. Second, it declares an event named "click", which can be used outside the class according to regular access rules (in this example, the event is a public event ).
A function like onclick () is usually included so that this type or a derived type from this type can trigger an event. Since "click" is a proxy, you will notice that the code used to trigger the event is the same as the proxy code.
Similar to the proxy, we use + = and-= to hook up or remove the event. However, unlike the proxy, we can only perform these operations on the event. This ensures that the two errors discussed previously do not occur.
Using events is simple.
Class Test
{
Static void clickfunction (Object sender, eventargs ARGs)
{
// Process the event here.
}
Public static void main ()
{
Myobject = new myobject ();
Myobject. Click + = new myobject. clickhandler (clickfunction );
}
}
We create a static function or member function that matches the proxy signature, and then add a new proxy instance to the event with ++ =.