Author: Stanley B. Lippman from Fangzhou blog
[Translation: this is an old one Article . But there is no doubt that Lippman's explanation of delegate is brilliant.]
If you want to compare C # with other "C family" languages, C # has an unusual feature, in C ++ or Java, there is no real correspondence.
--------------------------------------------------------------------------------
C # is a controversial emerging language developed and created by Microsoft as the cornerstone of Visual Studio. NET. It is currently in the release phase of the first beta version. C # combines many features from C ++ and Java. The main criticism of the Java Community for C # Is that it claims that C # is just a lame Java clone version-rather than a result of language innovation, it is a result of litigation. In the C ++ community, the main criticism (also targeting Java) is, C # Is just another popular private language (yet another over-hyped proprietary language ).
This article is intended to demonstrate a C # language feature, but does not directly support similar features in C ++ or Java. This is the delegate type of C #, which operates like a pointer to a member function. In my opinion, C # Delegate type is a well-thought-out innovative language feature, C ++ProgramPersonnel (no matter what they think about C # or Microsoft) should have a special interest in this feature.
To stimulate the discussion, I will elaborate on the design of a testharness class. This testharness class allows any class to register static or non-static class methods for subsequent execution. The delegate type is the core of the implementation of testharness class.
C #'s delegate type
Delegate is a function pointer, but compared with common function pointers, there are three main differences:
1) A delegate object can carry multiple methods at a time (methods) [1] instead of one at a time. When we call a delegate carrying multiple methods, all methods are called in order of the order in which they are loaded with delegate objects. Let's take a look at how to do this.
2) The method (methods) carried by a delegate object does not need to belong to the same category. All methods (methods) carried by a delegate object must have the same prototype and form. However, these methods can be either static or non-static and can be composed of one or more members of different categories.
3) A delegate type statement creates a new subtype instance, which is derived from. net Library Framework abstract base classes delegate or multicastdelegate, they provide a set of public methods for querying the delegate object or its carrying method (methods)
Declare delegate type
The declaration of a delegate type is generally composed of four parts: (a) access level; (B) keyword delegate; (c) return type, and the declaration form (Signature) of the method carried by the delegate type; (d) Name of the delegate type, placed between the return type and the method declaration form (signature. For example, the following declares a public delegate type action, which is used to carry the "No parameter and has a void return type" method:
Public Delegate void action ();
At a glance, this is surprisingly similar to the function definition. The only difference is that the delegate keyword is added. The purpose of adding this keyword is to differentiate common member functions from other similar syntax forms by using the keyword (keyword) instead of the character (token. In this way, virtual, static, and delegate are used to distinguish between various functions and syntax forms like functions.
If a delegate type carries only one method at a time, it can carry any type and form of returned member functions. However, if a delegate type must carry multiple methods at the same time, the return type must be void [2]. For example, action can be used to carry one or more methods ). In the implementation of testharness class, we will use the above action declaration.
Define delegate handle
In C #, we cannot declare global objects. Each object definition must be a local object, a type object member, or a parameter in the function parameter list. Now I will only show you the delegate type declaration. Then let's take a look at how to declare it as a member of the category.
The delegate type in C # is the same as the class, interface, and array types, and belongs to the reference type. Each reference type is divided into two parts:
A named handle (named handle), which is directly manipulated by us; and
An unamed object of the type to which the handle belongs is indirectly manipulated by the handle. The object must be explicitly created through new.
Defining reference type is a two-step process. When we write:
Action theaction;
Theaction represents a handle (handle) of the "delegate type action object", which is not a delegate object. By default, it is set to null. If we try to use assigned before assigning values to it (attachment with the corresponding type object), a compilation error will occur. For example, the statement:
Theaction ();
Method (s) carried by theaction )). However, unless it is unconditionally assigned a value after its definition and before its use ), otherwise, the statement will cause a compilation error and print the relevant information.
Allocate space for delegate object
In this section, we need to access a static method and a non-static method to minimize the scope of the description ), in this case, I used an announce class. The announcedate static method of this type prints the current date to the standard output device in the form of long form (using the lengthy form of the complete word:
Monday, February 26,200 1
Non-static method announcetime prints the current time to the standard output device in the short form (short representation:
00:58
the first two digits represent the hour, which starts from midnight and the last two digits represent the minute. The announce class uses the datetime class provided by the. NET class framework. The announce category is defined as follows.
public class announce
{< br> Public static void announcedate ()
{< br> datetime dt = datetime. now;
console. writeline ("Today ''' s date is {0}",
DT. tolongdatestring ();
}< br> Public void announcetime ()
{< br> datetime dt = datetime. now;
console. writeline ("the current time now is {0}",
DT. toshorttimestring ();
}< BR >}
To enable theaction to carry the preceding method, we must use the new expression to create an action delegate type ). To carry a static method, the introduced constructor consists of three parts: the name of the category of the method, the name of the method, and the dot operator (.) used to separate the two names (.):
Theaction = new action (announce. announcedate );
To carry a non-static method, the introduced constructor is composed of three parts: the Class Object Name to which the method belongs; the name of the method; separate Two names with dot operator (.):
Announce an = new announce ();
Theaction = new action (an. announcetime );
It can be noted that theaction is directly assigned a value without any check in advance (for example, check whether it refers to an object in a heap, and if so, delete the object first ). In C #, objects in managed heap are subjected to garbage collection (garbage collected) by runtime environments ). We do not need to explicitly Delete objects allocated through the new expression.
In the managed heap (managed heap) of the program, the new expression can be allocated to a single object.
Hellouser myprog = new hellouser ();
You can also assign an array object.
String [] messages = new string [4];
The allocation statement is a type name followed by the keyword new, followed by a pair of circular arc (representing a single object) or square brackets (representing an array object) [1]. (A common feature in C # language design is to insist on using a single clear form to differentiate different functions .)
A quick overview: Garbage Collection)
As shown in the following array object, when we allocate space for the reference type in the managed heap (managed heap:
Int [] fib = new int [6 };
The number of handles that the object automatically maintains. In this example, an associated Reference Counter of the array object pointed to by fib is initialized to 1. If we initialize another handle to point to the array object referred to by fib:
Int [] notfib = fib;
This initialization resulted in a shallow copy (shallow copy) of the array object referred to by fib ). That is to say, notfib now points to the array object pointed to by fib. The reference count associated with the array object is changed to 2.
If we modify an element in the array through notfib, for example
Notfib [0] = 0;
This change is also visible to fib. If this multi-access method to the same object is not required, we need to writeCodeTo make a deep copy (deep copy ). For example,
// Allocate another array object
Notfib = new int [6];
// Starting from the 0th elements of notfib,
// Copy the elements in fib to notfib in sequence.
// See note [2].
FIB. copyto (notfib, 0 );
Notfib does not refer to the object referred to by fib now. The objects that were previously pointed to by both of them subtract 1 from the associated reference count. The initial reference count of the object referred to by notfib is 1. If we re-assign fib to a new array object, for example, an array containing the first 12 values of the Fibonacci series:
FIB = new int [12] {89,144 };
For the array object referred to by fib, its reference count is now 0. In the managed heap (managed heap), when the garbage collector is active, the object with a reference count of 0 is marked as deleted.
Define class Properties
Now let's declare the delegate object as a private static member of testharness class. For example, [3],
Public class testharness
{
Public Delegate void action ();
Static private action theaction;
//...
}
In the next step, we will provide a read/write access mechanism for the delegate member. In C #, do not provide explicit inline methods for reading and writing non-public data members. Instead, we provide get and set accessors for the named property ). Below is a simple delegate property. We may call it Tester:
Public class testharness
{
Static public action Tester
{
Get {return theaction ;}
Set {Action = value ;}
}
//...
}
Property can encapsulate both static data members and non-static data members. Tester is a static property of the delegate type action ). (You can note that. We define the accessor as a code block. The compiler generates the inline method internally .)
Get must use the property type as the return type. In this example, it directly returns the encapsulated object. If "lazy allocation" is used, get can be constructed and stored when it is aroused for later use.
Similarly, if we want property to support write-type access, we will provide set accessor. Value in set is a conditional keyword (conditional-Keyword ). That is to say, value only has a predefined meaning in the Set property. (Note: value is only considered as a keyword in the Set code segment). In our example, value is an action-type object. During running, it is bound to the right side of the value assignment expression. In the following example,
Announce an = new announce ();
Testharnes. tester =
New testharness. Action
(An. announcetime );
Set inline is expanded to the place where tester appears. The value object is set to the object returned by the new expression.
Call up delegate object
As we have seen before, to invoke the method carried by Delegate, we apply call operator (Circular Arc pair) to delegate ):
Testharness. Tester ();
This sentence calls the get accessor of tester property; get accessor returns theaction delegate handle. If theaction does not point to a delegate object at the moment, an exception will be thrown. The following code uses delegate-test-and-execute to invoke actions outside of a category:
If (testharness. tester! = NULL)
Testharness. Tester ();
For testharness class, our method only encapsulates such a test:
Static public void run ()
{
If (theaction! = NULL)
Theaction ();
}
Associate multiple delegate objects
To enable a delegate to carry multiple methods, we mainly use + = Operator and-= Operator. For example, suppose we define a testhashtable class. In the constructor, we add the associated tests to testharness:
Public class testhashtable
{
Public void test0 ();
Public void test1 ();
Testhashtable ()
{
Testharness. tester + = new testharness. Action (test0 );
Testharness. tester + = new testharness. Action (test1 );
}
//...
}
Similarly, if we define a testarraylist class, we also add the associated test to the default constructor. Note that these methods are static.
Public class testarraylist
{
Static public void testcapacity ();
Static public void testsearch ();
Static public void testsort ();
Testarraylist ()
{
Testharness. tester + = new
Testharness. Action (testcapacity );
Testharness. tester + = new testharness. Action (testsearch );
Testharness. tester + = new testharness. Action (testsort );
}
//...
}
When the testharness. Run method is invoked, we usually do not know which method in testhashtable and testarraylist is invoked first; this depends on the sequence in which their constructors are called. However, we can know that for each category, the order in which methods are invoked is the order in which methods are added to delegate.
Delegate objects and garbage collection)
Evaluate the knowledge of the code segment in the following local scopes:
{
Announce an = new announce ();
Testharness. tester + =
New testharness. Action
(An. announcetime );
}
After a non-static method is added to the delegate object, the address of the method and the "Handle Used to evoke the method and point to the Class Object" are all stored. This will automatically increase the reference count associated with the category object.
After an is initialized using the new expression, the reference count associated with objects in the managed heap is initialized to 1. After an is passed to the delegate object constructor, the reference count of the announce object is increased to 2. After going out of the local scope, the survival of an ends, and the reference count is reduced to 1 -- the delegate object still occupies one.
The good news is that if a delegate references a method of an object, this ensures that the object will not be Spam until "delegate object no longer references this method" [4]. We don't have to worry that the object will be immediately cleared under our own eyes. The bad message is that the object will continue to exist until the delegate object no longer references its method. You can use-= Operator to remove this method from the delegate object. For example, the code in the revised version below; in a local scope, announcetime is set and executed first, and then removed from the delegate object.
{
Announce an = new announce ();
Action Act = new testharness. Action (an. announcetime );
Testharness. tester + = Act;
Testharness. Run ();
Testharness. tester-= Act;
}
Our initial idea for the design of testhashtable class is to implement an destructor to remove the testing method added to the constructor. However, the destructor calling mechanism in C # is not the same as that in C ++ [5]. The destructor of C # will not be aroused because of the end of the object's lifetime, or because the last reference handle of the object is released. In fact, destructor are called only when the garbage collector is used for garbage collection. However, the timing of garbage collection is generally unpredictable, and even garbage collection is not implemented at all.
C # indicates that the resource de-allocation is completed in a method called dispose. You can directly call this method:
Public void dispose ()
{
Testharness. tester-= new testharness. Action (test0 );
Testharness. tester-= new testharness. Action (test1 );
}
If a class defines an destructor, it usually calls dispose.
Access the underlying category Interface
Let's look back at the previous Code:
{
Announce an = new announce ();
Action Act =
New testharness. Action
(An. announcetime );
Testharness. tester + = Act;
Testharness. Run ();
Testharness. tester-= Act;
}
Another implementation scheme is to first check whether tester is currently carrying other methods. If yes, save the current delegate list (delegation list) and reset tester to act, then call run to restore the tester to its original state.
We can use the underlying delegate class interface to learn the actual number of methods supported by Delegate. For example,
If (testharness. tester! = NULL &&
Testharnest. getinvocationlist (). length! = 0)
{
Action oldact = testharness. Tester;
Testharness. tester = Act;
Testharness. Run ();
Testharness. tester = oldact;
}
Else {...}
Getinvocationlist returns the delegate class objects array. Each element of the array represents a method currently supported by the delegate. Length is a property of the underlying array class ). Array class implements C # built-in array semantics [6].
Through the method property of the delegate class, we can obtain all the runtime information of the loaded method. If the method is non-static, the target property of the delegate class allows us to obtain all the runtime information of the object that calls the method (the object of the category of the method. In the following example, the delegate methods (method) and properties (properties) are represented in Red:
If (testharness. tester! = NULL)
{
Delegate [] Methods = test. tester. getinvocationlist ();
Foreach (delegate D in methods)
{
Methodinfo thefunction = D. method;
Type thetarget = D. Target. GetType ();
// OK: now we can know all the information about the method carried by Delegate.
}
}
Summary
I hope this article will interest you in C # Delegate type. I think the delegate type is C #, which provides an innovative "pointer to class method (class method pointer)" mechanism. Maybe this article has aroused your interest in the C # language and. Net class framework.
A good starting page for technical resources is
Thank you
I wowould like to thank Joseph E Lajoie and Marc Briand for their thoughtful review of an earlier draft of this article. their feedback has made this a significantly better article. I wowould also like to thank Caro Segal, Shimon Cohen, and Gabi Bayer of you-niversity.com for providing a safety. net.
Note
[1] for C ++ programmers, there are two points worth mentioning: (a) a pair of Circular Arc should be placed after the object type name as the default constructor, and (B) square brackets used for the array subject should be placed between the type and the array name.
[2] The built-in array in C # is an object of array class provided by. Net class library. The static and non-static methods of array class can be used by C # built-in array objects. Copyto is a non-static method of array.
[3] Like java, the member declaration in C # includes its access level. The default access level is private.
[4] Similarly, the C ++ standard requires that the referenced temporary object cannot be destroyed until the end of the referenced lifetime.
[5] within an internal implementation, destructor have never even existed. A class destructor is converted to the virtual Finalize method.
[6] in C #, the result of a conditional discriminant must be of the boolean type. The direct determination of the length value, such as if (testharness. length), is not a legal condition judgment. Integer values cannot be implicitly converted to boolean values.
Stanley B. lippman is it Program chair with you-niversity.com, an interactive e-learning provider of technical courses on patterns, C ++, C #, Java, XML, ASP, And. NET platform. previusly, Stan worked for over five years in feature animation both at the Disney and DreamWorks Animation Studios. he was the software technical ctor on the Firebird segment of Fantasia 2000. prior to that, Stan worked for over a decade at Bell Laboratories. stan is the author of C ++ primer, essential C ++, and inside the C ++ object model. he is currently at work on C # primer for the developmentor book series for Addison-Wesley. he may be reached at stanleyl@you-niversity.com.
Annotation
[1] in C #, the so-called "method" is actually a member function that we usually understand. Its literal meaning is similar to that of "function) "Very close.
[2] The author has this statement for the aforementioned delegate type Action Statement. In general, as long as the return types of multiple methods are the same and the parameters are the same, it can be carried by the same delegate type.
Recommendation: write faster managed code: Understand overhead
Http://www.microsoft.com/china/msdn/archives/library/dndotnet/html/fastmanagedcode.asp
310674-how to: Add references to hosted visual c ++ Projects
Http://support.microsoft.com/default.aspx? SCID = KB; ZH-CN; 310674