C # 2.0 Introduction
C # 2.0 Introduces a lot of language extensions, the most important thing is generics, anonymous methods, an iterators, and incomplete types (Partial 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 generic generics allow class, structure, interface, entrustments, and methods to parameterize through the types 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, using objects to accept and return data, respectively: public class stack {object [] items; int count Public void push (Object Item) {...} public object pop () {...}} Although the use Object type makes the Stack class is very flexible, it does not have no shortcomings. For example, you can press any type of value in the stack, such as a Customer instance. However, re-retrieving a worthwhile translation of the value returned by the POP method 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, passed to the PUSH method, which will automatically pack the box. When the INT value is retrieved, the explicit type conversion must be expanded: stack stack = new stack (); stack.push (3); int i = (int) stack.pop (); this The packing and unpacking operations increase 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 when retrieving it, it will accidentally convert into an error type: stack stack = new stack (); stack.push (new customer ()); string s = STRING.POP (); Although the above code is an incorrect usage of the Stack class, this code is technically correct 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 generic generics provide a tab to establish a type with type parameters (Type Parameters). The following example declares a generic STACK class with type parameter T. Deterse "<" and ">" specified after type parameters. An instance of STACK created by some type can be accepted by this type of data, which is too excreted with Object. The type parameter T play a role of a placeholder until an actual type is specified. Note T is equivalent to the data type of the internal array, the parameter type of the PUSH method and the return value type of the POP method: Public Class Stack {T [] items; int count; 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 following example, specify INT as a parameter type T: stack stack = new stack (); stack.push (3); int x = stack.pop (); stack type is called already Construct Type (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 only operate the int value, and if the value in which other types of values in the stack will get the error during compilation, and when a value is retrieved, it is not necessary to display it to the original type. 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 the int value, and Stack can only operate the Customer object. 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 the error int x = stack.pop (); // Type does not match the wrong gear gear type declaration allows any number of type parameters.
The above Stack Example There is only one type parameter, but a generic Dictionary class may have two types of parameters, one is the type of key to the other of the key: PUBLIC CLASS DICTIONARY {public void Add (K Key, V Value) {...} public v this [k key] {...}} Two types of parameters need to be provided when using Dictionary : Dictionary DICT = NEW Dictionary (); Dict.Add ("Peter", New Customer ()); Customer C = DICT ["Peter"]; 19.1.3 Pan type instantiation and non-float type Similar, compiled The generic type is also represented by the intermediate language (IL, Intermediate Language) instruction 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 Stack ,. NET Public Language Running Pan IL and metadata Convert to a local code and replace the type parameters 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 Constraints usually, a generic class does not only store data based on a certain type of parameters, and he also calls a method of a given type object. For example, the Add method in Dictionary may need to use the CompareTo method to compare key values: 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 parameter key with the presence of the member only has a member from Object, 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 works, the dynamic type conversion will increase during runtime, which will increase 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 make Dictionary class can guarantee the key value to always implement the IComparable interface, 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) {...} ...}} With this statement, the compiler guarantees that all types provided to the type parameter K are implemented. IComparable interface. 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 , IPERSISTABLEEE E: ENTITY, New () {public void add (k Key, e entity) {... IF (key.Compareto (x) <0) {...} ...}} The constructor constraint in the above example, New () To ensure that the type of E-type variable has a common, unfolded constructor, and allows generic classes to use new E () to create an instance of this type. 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 cannot be used for types that do not implement the IComparable interface, although the Sort method has never been actually called in this case. 19.1.5 When a generic method is, a type parameter is not necessary, 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 separately. For a specific type of constructed, such as Stack , this method looks like this: void pushmultiple (stack stack, params int [] value) {foreach (int value in value) stack.push () (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 constructed 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 [] values) {Foreach (T value in value) 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. For example: Stack stack = new stack (); pushmultiple (stack, 1, 2, 3, 4); this generic PushMultiPle method is more reusable than the above version because it It can work 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. In the above example, since the type of the first official parameter is stack , and the following parameter type is int, the compiler can determine the type parameter is Int. Therefore, you can not provide type parameters when calling generic PushMultiPle methods: stack stack = new stack (); pushmultiple (stack, 1, 2, 3, 4); 19.2 Anonymous method practice processing method and Other callback methods typically need to call through special delegates instead of calling. 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; bublic myform () {listbox = new listbox (...); addbutton = new button (...); addbutton .Click = new eventhandler (addClick);} void addclick (object sender, evenetargs e) {listbox.items.add (textbox.text);}} Although only one statement is only one statement for the button's CLICK event, this statement is also There must be an independent method with a completely parameter list, and to manually create an EventHandler delegation that references the method.
Using an anonymous method, the code processing code 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.items.add (TextBox.text);};}} An anonymous method consists of keyword delegate and an optional parameter list And put 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 include a list of parameters: addButton.Click = delegate (Object sender, Eventargs e) {messagebox.show ((Button Sender) .Text);}; above An implicit conversion has occurred between anonymous methods and EventHandler delegate types (Types of Click events). This implicit conversion is feasible because the parameter list and return value type and anonymous method are compatible. The accurate compatibility rules are as follows: • When there is a true when there is a true, the parameter list and anonymous method of the delegate are compatible: o Anonymous method does not have a parameter list and there is 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 true, the return value of the delegate is compatible with anonymous method: o The return value type of the delegate is a 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
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 anonymous method. 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 one of the delegate types that should be instantiated, but the effect is the same as the long form. 19.3 Iterative C # The Foreach statement in the iterator C # is used to iterate an element in an enumerable (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 block by one or more Yield statements that appear: • Yield RetURN statement produces 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 (Enumerator Interfaces) or an enumerable interface (Enumerable Interfaces), we can use iterators: • The so-called enumerator excuse refers to system.collections.ienumerator and from System.collections.Generic.ienumerator The type of constructor. • The so-called enumeration interface refers to the type of system.collections.ienumerable and from system.collections.generic.ienumerable . 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 the 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 count; public void push (t data) {...} public t pop () {...} public TPU IENUMERATOR getEnumerator () {for (int i = count - 1; i> = 0; --i) {yield return items;}}} GetEnumerator method has enabled STACK into an enumerable type, This allows instances of Stack 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 ();}} This example is: 9 8 7 6 5 4 3 2 1 0 The statement is implicitly called the collection GetEnumerator method 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 - INumerable TopTobottom and Bottomtotop: use system.collections.Generic; Public Class Stack : ienumerable {t [] items; int County; public void push (t data) {...} public t pop () {...} public ienumerator getEnumerator () {for (int i = count - 1; i> = 0; --I ) {yield return items;}} public} {get {return this;}} public}}} {get {for (int i = 0; i
The following example shows how to use these two properties to enumerate the elements in any order: use 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 properties can also be used outside the foreach statement. The following example passes the result of the call attribute to a separate Print method. This example also demonstrates an iterator as a method for use as a FROMTOBY method: use system; use means system.collections.Generic; class test {static void print (IENUMERABLE Collection) {Foreach (INT I IN) Collection) Console.Write ("{0}", i); console.writeline ();} static → → → → int in in) {for (int = 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));}} This example is: 9 8 7 6 5 4 3 2 1 00 1 2 3 4 5 6 7 8 910 12 14 16 Emotive and non-extensive accessible connections There is only a single member, a non-arranging GetEnumerator method, which returns an enumerator interface. A enumerable interface is like an enumerator factory (ENUMERATOR factory). Whenever a GeTenumerator method that correctly implements an enumerated interface, a separate enumerator is generated. Using system; usneric; class test {static {while (from <= to) yield return from ;} static void main () {ienumerable e = FROMTO (1, 10); Foreach (int x in e) {finch (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.