[Translation] lover_p 2004-01-26
-------------------------------------------------- ------------------------------
-------------------------------------------------- ----------------------------
Since the official reference materials of Microsoft, users who provide our computer science and technology network learn from each other, and anyone must not be used for commercial purposes. Please indicate the source.
-------------------------------------------------- ----------------------------
C # 2.0 Introduces a lot of language extensions, the most important thing is generics, anonymous methods, an iterators, and incomplete types.
• The generic allows class, structures, interfaces, entrustments and methods to parameterize through the types of data they store and operate. The generic is useful because it provides a more powerful compilation type check, requires a few different data types, and reduces the type check of the need for the packing operation and the runtime check.
• The anonymous method allows you to write code blocks in "in-line" by "in-line" when you need a credit value. An anonymous method is similar to the Lisp language in the Lisp language.
• Itecher is capable of incrementally calculating and generating a series of worthwhile devices. The iterator makes a class easily explains how the Foreach statement will iterate each of his elements.
• Incomplete types allow class, structure, and interfaces being divided into multiple small pieces and store them in different source files make it easy to develop and maintain. In addition, the incomplete type can separate the code generated by the machine and the part written by the user, which makes it easy to use the tool to enhance the result.
This chapter first makes these new features a profile. After the introduction, there are four chapters, providing the complete technical specifications for these features.
The language extension in C # 2.0 ensures high compatibility with existing code. For example, although C # 2.0 gives a special meaning of words WHERE, Yield and Partial in a specific environment, these words can be used as identifiers. Indeed, C # 2.0 does not add a keyword conflict with the identifier in the existing code.
19.1 generics
The generic allows classes, structures, interfaces, entrustments, and methods to parameterize the type of data they store and operate. C # generics is quite intimate to users using Eiffel or Ada language generic and users using C templates, although they may not endure the complexity of the latter.
19.1.1 Why is generic?
There is no generic, and some general data structures can only be used to store various types of data using an Object type. For example, the simple STACK class puts its data in an Object array, and its two methods, push, and pops are accepted and returned by using Object:
Public Class Stack
{
Object [] items;
INT country;
Public void push (Object item) {...}
Public Object Pop () {...}
}
Although the use Object type makes the Stack class is very flexible, it is not shortcoming. For example, you can press any type of value in the stack, such as a Customer instance. However, re-retrieving a worthwhile direction, the value returned by the POP method must be reversed to the appropriate type, writing these conversion changes to be aware of the runtime type check error is very bored:
Stack stack = new stack (); stack.push (new customer ());
Customer C = (Customer) stack.pop ();
If a value of a value type, such as int, passes to the PUSH method, it will automatically pack it. When you retrieve this int value, you must have an expanded type conversion:
Stack stack = new stack ();
STACK.PUSH (3);
INT i = (int) stack.pop ();
This packing and unpacking operation increases the burden of execution because it brings dynamic memory allocation and runtime type check.
Another problem with the Stack class is the type of data in the stack. Indeed, a Customer instance can be pressed into the stack, and accidentally convert into a wrong type when retrieving it:
Stack stack = new stack ();
Stack.push (New Customer ());
STRING S = (String) stack.pop ();
Although the code above is an incorrect usage of the Stack class, this code is correct from the technical and does not occur during compilation. When you know this code, you will appear when you run, and you will throw an invalidcastexception.
The Stack class will undoubtedly benefit from the ability to qualify the type of elements. This will become possible using generics.
19.1.2 Establishing and using generics
The generic provides a technique to establish a type with type parameters. The following example declares a generic STACK class with type parameter T. Deterse "<" and ">" specified after type parameters. An instance of STACK-established Stack can accept this type of data in no desire to transform each other with Object. The type parameter T play a role of a placeholder until an actual type is specified. Note that T is equivalent to the data type of the internal array, the parameter type of the PUSH method and the return value of the POP method:
Public Class Stack
{
T [] items;
INT country;
Public void push (t item) {...}
Public T Pop () {...}
}
When using generic class STACK, you need to specify the actual type to replace T. In the example below, specify INT as a parameter type T:
Stack stack = new stack ();
STACK.PUSH (3);
INT x = stack.pop ();
The Stack type is called a constructed type. All T that appears in the Stack type is replaced with the type parameter INT. When an instance of a Stack is created, the local storage of the items array is int [] instead of Object [], which provides a substantially stored, efficient, high-free STACK. Similarly, the PUSH and POP methods in Stack operate only the INT value. If the value in which other types of values to the stack will get an error during compilation, and it is not necessary to convert it to the original type when retrieving a value.
The generic can provide a strong type, which means that an error will be generated, for example, pressing an Int in a stack of a Customer object. This is because STACK can only operate int values, and Stack can only operate Customer objects. The last two lines in the following example will cause the compiler to report error:
Stack stack = new stack ();
Stack.push (New Customer ());
Customer c = stack.pop ();
Stack.push (3); // type does not match errors
INT x = stack.pop (); // type does not match the declaration of error fluxing types Allow any number of type parameters. The above Stack example has only one type of parameters, but a generic Dictionary class may have two types of parameters, one is the type of key to the other of the value:
Public Class Dictionary
{
Public Void Add (K Key, V Value) {...}
Public v this [k Key] {...}
}
When using Dictionary, you need to provide two types of parameters:
Dictionary dict = new dictionary ();
Dict.Add ("Peter", New Customer ());
Customer C = DICT ["Peter">
19.1.3 Instantization of generic types
Similar to non-float types, compiled generic types are also indicated by the intermediate language (IL, Intermediate Language) instructions and metadata. The generic type IL means that it has been encoded by the type parameters.
When the program creates an instance of a constructed generic type, such as the instant compiler (JIT, Just-in-Time) in the Stack, .NET public language is converted to a local Code and replace the type parameters in the process in the process. The subsequent use of the same local code is used for this type of generic type of a structure. The process of establishing a specific type of constructor from generic type is called generic type instantiation.
The .NET public language is run for each type of generic type, which is instantiated by the type, and all reference types share a separate copy (because of the local code level, the reference knowledge has the same performance " ).
19.1.4 constraint
Typically, a generic class will not only store data based on a certain type of parameters, and he also calls a method of a given type of object. For example, the Add method in Dictionary may need to use the CompareTo method to compare the key value:
Public Class Dictionary
{
Public Void Add (K Key, V Value)
{
...
IF (Key.Compareto (x) <0) {...} // error, no Compareto method
...
}
}
Since the specified type parameter k can be any type, you can assume that the presented parameter key has only members of the member, such as Equals, GetHashcode, and Tostring; therefore, the above example will compile errors. Of course, the parameter key can be converted into a type with a Compareto method. For example, the parameter key can be converted to ICOMPARABLE:
Public Class Dictionary
{
Public Void Add (K Key, V Value)
{
...
IF ((iComparable) key) .compareto (x) <0) {...}
...
}
}
When this scheme is working, it will cause dynamic type conversion at runtime, which will increase the overhead. More destined is that it may also post the error report to run. If a key does not implement the IComparable interface, it will throw an InvalidCastException exception.
In order to provide more powerful compilation type checks and reduced type conversions, C # allows an optional list of constraints available for each type of parameters. The constraint of a type parameter specifies a requirement that must be observed, so that this type of parameters can be used as a variable. The constraint is declared by the key word where, the name of the type parameters, and then the list of class or interface type, or the constructor constraints new (). To enable the Dictionary class to ensure that the iComparable interface is always implemented, the class parameter k should specify a constraint for the type parameter k:
Public Class Dictionary Where K: iComparable
{
Public Void Add (K Key, V Value)
{
...
IF (Key.Compareto (x) <0) {...}
...
}
}
Through this statement, the compiler guarantees that all ICOMPARABLE interfaces are implemented all the types of parameter k. Furthermore, it is no longer necessary to explicitly convert the key value to an IComparable interface before calling the CompareTo method; all members of a constrained type parameter type can be used directly.
For a given type parameter, you can specify any number of interfaces as constraints, but can only specify a class (as constraint). Each constrained type parameter has a separate WHERE clause. In the following example, the type parameter k has two interface constraints, and the type parameter E has a class constraint and a constructor constraint:
Public Class EntityTable
Where k: iComparable, IPERSISTABLE
WHERE E: E: E: E: E: E: E: E: E: E: E: E: E: E: E: E:
{
Public Void Add (K Key, E Entity)
{
...
IF (Key.Compareto (x) <0) {...}
...
}
}
The constructor constraints in the above example, NEW () ensures that the type of E-type variable has a common, no argument constructor, and allows generic classes to create an example of this type using NEW E ().
Be careful with the use of the type of parameter constraint. Although they provide more powerful compilation types and improve performance in some cases, it still limits the use of generic types. For example, a generic class List may constrain T to implement the IComparable interface so that the Sort method can compare the elements. However, this makes List can't be used for types that do not implement the IComparable interface, although the Sort method has never been actually called.
19.1.5 generic method
Sometimes a type parameter is not necessary for the entire class, but only in a particular method. Typically, this situation occurs when establishing a method requiring a generic type as a parameter. For example, when using the STACK class described above, a common mode is to press a plurality of values in a row, and if writing a method, it will be convenient to complete this work by calling it by separately. For a specific type of construct, such as stack, this method looks like this:
Void pushmultiple (stack stack, params int [] value) {
FOREACH (INT Value In Values) stack.push (value);
}
This method can be used to press multiple int values into a Stack:
Stack stack = new stack ();
Pushmultiple (stack, 1, 2, 3, 4);
However, the above method can only work in a specific type of Type Stack. To make him work in any Stack, this method must be written into a generic method. One generic method has one or more type parameters, and the "<" and ">" qualifiers behind the method name are specified. This type of parameters can be used in the parameter list, returns to and the method body. A generic Pushmultiple method looks like this: void pushmultiple (stack stack, params t [] value) {
FOREACH (T Value In Values) stack.push (value);
}
Using this method, multiple elements can be pressed into any STACK. When a generic method is called, put the type parameters in a plurality of brackets in the call of the function. E.g:
Stack stack = new stack ();
Pushmultiple (stack, 1, 2, 3, 4);
This generic PushmultiPle method is more reusable than the above version because it works in any Stack, but this doesn't feel uncomfortable because you must provide a type parameter for T. However, many times the compiler can infer the correct type parameters by passing other parameters of the method, which is called type inferion (Type Inferen). In the example above, since the type of the first formal parameter is STACK, and the next parameter type is int, the compiler can determine that the type parameter is Int. Therefore, you don't have to provide type parameters when calling generic PushMultiPle methods:
Stack stack = new stack ();
Pushmultiple (stack, 1, 2, 3, 4);
19.2 anonymous method
Practical treatment methods and other callback methods typically need to call through special delegates, not directly call. Therefore, so far we can only place a practical handling and callback code in a specific method, and then explicitly establish a delegate. Instead, anonymous method is allowed to associate code "in-line" to the place of use, we can easily write code directly in the entrustment instance. In addition to appearing, the anonymous method also shares access to the function members included in the local statement. If you want to reach this sharing in the naming method (distony anonymous method), you need to manually create an auxiliary class and upgrade local members (lifting "to this class.
The following example demonstrates a simple input from a form that contains a list box, a text box, and a button. When the button is pressed, the text in the text box is added to the list box.
Class InputForm: Form
{
Listbox listbox;
TextBox textbox;
Button addbutton;
Public myform () {
Listbox = new listbox (...);
Textbox = new textbox (...);
AddButton = New Button (...);
AddButton.Click = New EventHandler (AddClick);
}
Void AddClick (Object Sender, Eventargs E) {
Listbox.tems.add (TextBox.text);
}
}
Although there is only one statement to the button's Click event, this statement must be placed in a stand-alone method with a complete parameter list, and to manually create an EventHandler delegation that references the method. Using anonymous methods, the code processed by the event will become more concise: Class Inputform: Form
{
Listbox listbox;
TextBox textbox;
Button addbutton;
Public myform () {
Listbox = new listbox (...);
Textbox = new textbox (...);
AddButton = New Button (...);
AddButton.click = delegate {
Listbox.tems.add (TextBox.text);
}
}
}
An anonymous method consists of keyword delegate and an optional parameter list and places the statement into the "{" and "}" qualifiers. The anonymous method in the previous example does not use the parameters provided to the delegate, so the parameter list can be omitted. To access the parameters, your name should contain a list of parameters:
AddButton.Click = Delegate (Object sender, Eventargs E) {
Messagebox.show ((Button Sender) .text);
}
In the above example, an implicit conversion has occurred between anonymous methods and EventHandler delegate (Click events). This implicit conversion is feasible because the parameter list and return value type and anonymous method are compatible. Accurate compatibility rules are as follows:
• When there is a true in the following regulations, the delegated parameter list and anonymous method are compatible:
o Anonymous method There is no parameter list and has a delegate no output (OUT) parameter.
o The parameter list of an anonymous method is exactly matched with the delegate parameters on the number of parameters, type, and modifiers.
• When there is a true when there is a true, the return value of the commission is compatible with anonymous method:
o The return value type of the delegate is Void and an anonymous method does not have a return statement or its return statement without any expression.
o The return value type of the delegate is not a Void but the value of the RETURN statement associated with the anonymous method can be explicitly converted to the type of returned value.
The implicit conversion of anonymous type to the delegate type is only when the parameter list and the return value type are compatible.
The following example uses an anonymous method to "in-lian". The anonymous method is passed as a function delegate type.
Using system;
DELEGATE DOUBLE FUNCTION (Double X);
Class test
{
Static Double [] Apply (double [] a, function f) {
Double [] result = new double [a.length];
For (int i = 0; i Return Result; } Static double [] multiplyallby (double [] a, double factor) { Return Apply (a, DELEGATE (Double X) {Return X * Factor;}); } Static void main () { Double [] a = {0.0, 0.5, 1.0}; Double [] Squares = Apply (A, DELEGATE (Double X) {Return x * x;}); Double [] Doubles = MultiplyAllby (a, 2.0);} } The Apply method requires a given Double [] element and return Double [] as a function of function. In the main method, the second parameter passed to the Apply method is an anonymous method, which is compatible with the FUNCTION commission. This anonymous method simply returns the square value of each element, so the Double [] containing the Apply method is included in the square value of each value in A. The MULTIPLYALLBY method creates a double [] by multiplying each value in the parameter array by a given Factor. In order to generate this result, the multiplyAllby method invoked the Apply method to pass an anonymous method that multiplies the parameter X and Factor. If a local variable or parameter scope includes an anonymous method, the variable or parameter is called an external variable of an anonymous method (Outer variables). In the MultiplyLBY method, A and Factors are external variables that pass an anonymous methods that are passed to the Apply method. Typically, a local variable survives is limited to the statement within the block or in association with it. However, a survival period of the captured external variable is extended to at least a delegate reference to anonymous methods that meet the garbage collection conditions. 19.2.1 Method Group Conversion As described in the previous section, an anonymous method can be implicitly converted to a compatible entrustment type. C # 2.0 allows the same conversion to a set of methods, that is, an explicit instantiation of the delegate can be omitted. For example, the following statement: AddButton.Click = New EventHandler (AddClick); Apply (A, New Function (Math.sin)); You can also write: AddButton.click = addClick; Apply (a, math.sin); When using a short form, the compiler can automatically infer what the delegate type should be instantiated, but the effect is the same as the long form. 19.3 iterator The Foreach statement in C # is used to iterate an element in a collection of enumerable. In order to achieve an enumerated, a collection must have a GetNumerator method that returns an enumerator (Enumerator). Usually, the enumerator is difficult to implement, so simplify the task meaning of the enumerator. Iterator is a statement block that produces an ordered sequence that can generate a (yields) value. The iterator distinguishes the general statement by one or more Yield statements that appear: • Yield Return statement generates the next value of this iteration. • YIELD BREAK statement indicates that this iteration is completed. As long as a function member's return value is an enumerator interface (Enumerable Interfaces), we can use iterators: • The so-called enumerator excuse refers to the type of system.collections.ienumerator and from system.collections.generic.ienumerator. • The so-called enumeration interface refers to system.collections.ienumerable and the type constructed from System.Collections.Generic.Ienumerables. It is important to understand that it is not a member, but a functional member is important. A member implemented by an iterator can be overwritten or rewritten with one or not using or not using a member of the iterator. The following Stack class uses an iterator to implement its GetEnumerator method. The iterator enumerates the elements in the stack in the order from the top to the bottom end. Using system.collections.Generic; Public Class Stack: ienumerable { T [] items; INT country; Public void push (t data) {...} Public T Pop () {...} Public ienumerator getenumerator () { For (INT i = count - 1; I> = 0; --I) { Yield Return Items [i]; } } } The emergence of the GetEnumerator method makes STACK an enumerable type, which allows Stack's instance to use the Foreach statement. The following example presses the values 0 to 9 into an integer stack, and then use the Foreach loop to display each value in the order from the top to the bottom end. Using system; Class test { Static void main () { Stack stack = new stack (); For (int i = 0; i <10; i ) stack.push (i); Foreach (INT I in Stack) Console.Write ("{0}", i); Console.writeLine (); } } The output of this example is: 9 8 7 6 5 4 3 2 1 0 The statement implicitly invokes the collection of uncommon GetENUMERATOR methods to get an enumerator. A collection class can only define such a non-arranging GetENUMERATOR method, but enumerations can be implemented in many ways, including using parameters to control enumeration. In these cases, a collection can use an iterator to implement the properties and methods that can return the enumerated interface. For example, Stack can introduce two new properties - IENUMERABLE Type TopTobottom and Bottomtotop: Using system.collections.Generic; Public Class Stack: ienumerable { T [] items; INT country; Public void push (t data) {...} Public T Pop () {...} Public ienumerator getenumerator () { For (INT i = count - 1; I> = 0; --I) { Yield Return Items [i]; } } Public IEnumerable TopTobottom { Get { Return this; } } Public IEnumerable bottomtotop { Get { For (int i = 0; i Yield Return Items [i]; } } } } The GET Accessor of the TopTobottom property returns only this because the stack itself is an enumerable type. The Bottomtotop property returns an enumerated interface using the C # iterator. The following example shows how to use these two properties to enumerate the elements in any order: Using system; Class test { Static void main () { Stack stack = new stack (); For (int i = 0; i <10; i ) stack.push (i); Foreach (int I in stack.toptobottom) Console.write ("{0}", i); Console.writeLine (); Foreach (Int i in stack.bottomtotop) console.write ("{0}", i); console.writeline (); } } Of course, these attributes can also be used outside of the foreach statement. The following example passes the result of the call attribute to a separate Print method. This example also shows a method body for an iterator being used as a band-ginseng method: Using system; Using system.collections.Generic; Class test { Static Void Print (IEnumerable Collection) { Foreach (Int I in Collection) Console.Write ("{0}", i); Console.writeLine (); } Static IEnumerable fromToby (int from, int to, int reference) { For (int i = from; i <= to; i = by) { Yield Return I; } } Static void main () { Stack stack = new stack (); For (int i = 0; i <10; i ) stack.push (i); Print (stack.toptobottom); Print (stack.bottomtotop); Print (FromToby (10, 20, 2)); } } The output of this example is: 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 10 12 14 16 18 20 The generic and non-floating enumeration interfaces have only one single member, a non-arranging GetENUMERATOR method, which returns an enumerator interface. A enumerable interface is like an enumerator factory. Whenever a GeTenumerator method that correctly implements an enumerated interface, a separate enumerator is generated. Using system; Using system.collections.Generic; Class test { Static IEnumerable fromto (int from, int to) { While (from <= to) Yield Return from ; } Static void main () { IEnumerable E = fromTo (1, 10); Foreach (int x in e) { Foreach (int y IN E) { Console.write ("{0,3}", x * y); } Console.writeLine (); } } } The above code prints a simple multiplication table from 1 to 10. Note that the FROMTO method only calls once to generate an enumerated interface E. The E.GETENUMERATOR () is called multiple times (through the foreach statement) to generate multiple identical enumerators. These enumerators have encapsulated the code specified in the FROMTO statement. Note that iterates its code to change the from parameter. However, the enumerator is independent, because for the from parameters and TO parameters, each enumerator has its own copy. When implementing an enumerated and enumerator, the transition state (a unstable state) between the enumerator is one of the many fine slight flaws that must be eliminated. The design of the iterator in C # can help eliminate these problems, and can achieve robust enumeration and enumerator classes in a simple instinct. 19.4 Incomplete type Although all types of code in a single file are a good programming practice, but sometimes, when a class becomes very large, this has become an unrealistic constraint. Moreover, programmers often use code generators to generate an initial structure of an application and then modify the generated code. Unfortunately, when you need to post the original code again, the existing correction will be rewritten. Incomplete types allow class, structural and interfaces being divided into multiple small pieces and store them in different source files make it easy to develop and maintain. In addition, the incomplete type can separate the code generated by the machine and the part written by the user, which makes it easy to use the tool to enhance the result. To define a type in multiple sections, we use a new modifier -Partial. The following example implements an incomplete class in two parts. These two parts may be in different source files, for example, the first part may be generated by the machine through the database shadows, and the second part is manual creation: Public Partial Class Customer { Private int ID; PRIVATE STRING NAME; PRIVATE STRING ADDRESS PRIVATE LIST ORDERS; Public Customer () { ... } } Public Partial Class Customer { Public void submitorder (Order Order) { ORDERS.ADD (Order); } Public Bool HasoutStandingOrders () { Return ORDERS.COUNT> 0; } } When the two parts above are compiled together, the resulting code is as if this class is written in one unit: Public Class Customer PUBLIC CLASS { Private int ID; PRIVATE STRING NAME; PRIVATE STRING ADDRESS PRIVATE LIST ORDERS; Public Customer () { ... } Public void submitorder (Order Order) { ORDERS.ADD (Order); } Public Bool HasoutStandingOrders () { Return ORDERS.COUNT> 0; } } All parts of the incomplete type must be compiled together to merge them during compilation. It is important to note that incomplete type does not allow extension of compiled types