c#2.0 Introduction

Source: Internet
Author: User
Tags foreach anonymous array garbage collection new features variables sin sort
c#2.0 Introduction
C # 2.0 introduces many language extensions, most importantly generics (generics), anonymous methods (Anonymous Methods), iterators (iterators), and incomplete types (Partial Types).
• Generics allow classes, structs, interfaces, delegates, and methods to be parameterized by the type of data they are storing and manipulating. Generics are useful because they provide more powerful compile-time type checking, require fewer explicit conversions between data types, and reduce the need for boxing operations and type checking at run time.
• Anonymous methods allow you to write blocks of code in an "inline (in-line)" Way when you need a delegate value. The anonymous method is similar to the lambda function (lambda functions) in the Lisp language.
• Iterators are capable of incrementally calculating and generating a series of worthwhile methods. Iterators make it easy for a class to explain how a foreach statement will iterate over each of his elements.
• Incomplete types allow classes, structs, and interfaces to be divided into small chunks and stored in different source files to make them easy to develop and maintain. In addition, incomplete types can separate machine-generated code from user-written parts, making it easy to use tools to enforce the resulting code.
This chapter begins with a brief introduction to these new features. The introduction is followed by four chapters that provide complete technical specifications for these features.
The language extensions in C # 2.0 are designed to guarantee a high degree of compatibility with existing code. For example, although c#2.0 gives special meaning to the word where, yield, and partial in a particular environment, these words can be used as identifiers. Indeed, C # 2.0 does not add a keyword that will conflict with identifiers in existing code.
19.1 generic type
Generics allow classes, structs, interfaces, delegates, and methods to be parameterized by the type of data they are stored and manipulated. C # Generics are quite cordial to users who use the Eiffel or ADA language generics and to users who use C + + templates, although they may not tolerate the complexity of the latter.

19.1.1 why generics?
Without generics, some common data structures can only use object types to store various types of information. For example, the following simple stack class holds its data in an object array, and its two methods, push and pop, use object to accept and return data, respectively:
public class Stack
{
object[] items;
int count;
public void Push (item) {...}
public Object Pop () {...}
}
Although using the object type makes the stack class flexible, it is not without drawbacks. For example, you can push any type of value into the stack, such as a customer instance. However, retrieving a worthwhile time, you must explicitly convert the values returned by the Pop method to the appropriate type, writing these transformation changes to beware of run-time type checking errors is tedious:
Stack stack = new stack ();
Stack. Push (New Customer ());
Customer C = (customer) stack. Pop ();
If the value of a value type, such as int, is passed to the push method, it is automatically boxed. And when you retrieve this int value, you must have an explicit type conversion for the unboxing:
Stack stack = new stack ();
Stack. Push (3);
int i = (int) stack. Pop ();
This boxing and unboxing operation increases the burden of execution because it brings dynamic memory allocation and run-time type checking.
Another problem with the stack class is the inability to enforce the kind of data in the stack. Indeed, a customer instance can be pushed into the stack and accidentally converted to a wrong type when it is retrieved:
Stack stack = new stack ();
Stack. Push (New Customer ());
string s = (string) stack. Pop ();
Although the above code is an incorrect use of the stack class, this code is technically correct and no compile-time errors occur. When you know this code will run, it will throw a InvalidCastException exception.
The Stack class will no doubt benefit from the ability to qualify its element types. Using generics, this becomes possible.

19.1.2 Build and use generics
Generics provide a technique for creating types with type parameters (types parameters). The following example declares a generic stack class with type parameter T. The type parameter is followed by the delimiter "<" and ">" specified after the class name. An instance of a stack<t> that is established by a type can accept this type of data without any desire for conversion, which is too much to do with object. The type parameter T plays a placeholder role until an actual type is specified when it is used. Note T is the same as the data type of the internal array, the type of parameter accepted by the push method, and the return value type of the Pop method:
public class Stack<t>
{
t[] items;
int count;
public void Push (T item) {...}
Public T Pop () {...}
}
When using generic class stack<t>, you need to specify the actual type to replace T. In the following example, the int is specified as the parameter type T:
stack<int> stack = new stack<int> ();
Stack. Push (3);
int x = stack. Pop ();
The stack<int> type is called a constructed type (constructed type). All t that appear in the stack<int> type are replaced with the type parameter int. When an instance of a stack<int> is created, the local storage of the items array is int[] instead of object[], which provides a physical storage that is more efficient than a non-generic stack. The push and pop methods in the same,stack<int> only manipulate int values, and if you press another type of value into the stack, you will get a compile-time error, and you won't have to convert it to the original type when retrieving a value.
Generics can provide a strong type, which means that, for example, pressing an int onto a stack of a customer object will produce an error. This is because stack<int> can only manipulate int values, and stack<customer> can only manipulate Customer objects. The last two rows in the following example cause the compiler to make an error:
stack<customer> stack = new stack<customer> ();
Stack. Push (New Customer ());
Customer C = Stack. Pop ();
Stack. Push (3); Type mismatch error
int x = stack. Pop (); Type mismatch error
A declaration of a generic type allows any number of type parameters. The stack<t> example above has only one type parameter, but a generic dictionary class may have two type parameters, one is the type of the key and the other is the type of the value:
public class Dictionary<k,v>
{
public void Add (K key, V value) {...}
Public V this[k key] {...}
}
When using dictionary<k,v>, you need to provide two types of parameters:
Dictionary<string,customer> dict = new dictionary<string,customer> ();
Dict. ADD ("Peter", New Customer ());
Customer C = dict["Peter"];

19.1.3 generic type instantiation
Similar to non-generic types, compiled generic types are represented by intermediate language (IL, intermediate Language) directives, and meta data. The IL representation of a generic type is, of course, encoded by a type parameter.
When the program first establishes an instance of a constructed generic type, such as Stack<int> NET common language runtime the Just-in-time compiler (JIT, just-in-time) converts the generic IL and metadata to local code and replaces the type parameter with the actual type in the process. Later, the same local code is used for this reference to the constructed generic type. The process of building a particular constructed type from a generic type is called a generic type instantiation (generic type instantiation).
. NET common language runtime creates a specialized copy of each generic type instantiated by its type, while all reference types share a separate copy (because, at the local code level, references to the same performance are referenced).

19.1.4 constraints
Typically, a generic class does not simply store data based on a type parameter, but it also invokes a method of the given type of object. For example, the Add method in,dictionary<k,v> may need to use the CompareTo method to compare key values:
public class Dictionary<k,v>
{
public void Add (K key, V value)
{
...
if (Key.compareto (x) < 0) {...}//Error, no CompareTo method
...
}
}
Because the specified type parameter K can be any type, you can assume that the existing parameter key has members only from members of object, such as equals, GetHashCode, and ToString, so the example above will compile incorrectly. Of course, you can convert the parameter key to a type that has a CompareTo method. For example, the parameter key can be converted to IComparable:
public class Dictionary<k,v>
{
public void Add (K key, V value)
{
...
if ((IComparable) key). CompareTo (x) < 0) {...}
...
}
}
When this scenario works, it causes dynamic type conversions at run time and increases overhead. More importantly, it may also postpone the error report to the runtime. If a key does not implement the IComparable interface, a InvalidCastException exception is thrown.
To provide a more powerful compile-time type check and reduce type conversions, C # allows an optional list of constraints (constraints) that are provided for each type parameter. A constraint on a type parameter specifies a requirement that a type must comply with so that the type parameter can be used as a variable. Constraints are declared by the keyword where, followed by the name of the type parameter, then a list of class or interface types, or constructor constraint new ().
To enable the Dictionary<k,v> class to ensure that the key value always implements the IComparable interface, the class declaration should specify a constraint on the type parameter K:
public class dictionary<k,v> where k:icomparable
{
public void Add (K key, V value)
{
...
if (Key.compareto (x) < 0) {...}
...
}
}
With this declaration, the compiler is able to guarantee that all types supplied to type parameter K implement the IComparable interface. In turn, you no longer need to explicitly convert a key value to a IComparable interface before calling the CompareTo method, and all members of a value 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 you can specify only one class (as a constraint). Each type parameter that is constrained has a separate where clause. In the following example, type parameter K has two interface constraints, whereas type parameter E has a class constraint and a constructor constraint:
public class Entitytable<k,e>
where K:icomparable<k>, ipersistable
where e:entity, New ()
{
public void Add (K key, E entity)
{
...
if (Key.compareto (x) < 0) {...}
...
}
}
The constructor constraint in the example above, new (), guarantees that the type of the variable of type E has a public, parameterless constructor, and allows the generic class to use new E () to establish an instance of the type.
Use caution for type parameter constraints. Although they provide a more powerful compile-time type check and, in some cases, improved performance, it restricts the use of generic types. For example, a generic class list<t> may constrain T to implement the IComparable interface so that the sort method can compare elements. However, this does not allow list<t> to be used for types that do not implement the IComparable interface, although in this case the sort method has never been actually invoked.

19.1.5 generic method
Sometimes a type parameter is not necessary for the entire class, but only for a particular method. Typically, this happens when you create a method that requires a generic type as a parameter. For example, when using the Stack<t> class described earlier, a common pattern is to push multiple values in a row, and it is convenient to write a method to do this by calling its class individually. For a particular constructed type, such as Stack<int&gt, this method would look like this:
void Pushmultiple (stack<int> Stack, params int[] values) {
foreach (int value in values) stack. Push (value);
}
This method can be used to press multiple int values into a stack<int&gt:
stack<int> stack = new stack<int> ();
Pushmultiple (Stack, 1, 2, 3, 4);
However, the above method can only work on a specific constructed type of stack<int>. To make him work on any stack<t&gt, this method must be written as a generic method (generic). A generic method has one or more type parameters, which are specified by the "<" and ">" qualifiers after the method name. This type parameter can be used in the argument list, return to, and method body. The Pushmultiple method of a generic type would look like this:
void Pushmultiple<t> (stack<t> Stack, params t[] values) {
foreach (T value in values) stack. Push (value);
}
Using this method, you can push multiple elements into any stack<t>. When calling a generic method, enclose the type parameter in angle brackets in the call to the function. For example:
stack<int> stack = new stack<int> ();
Pushmultiple<int> (Stack, 1, 2, 3, 4);
This generic Pushmultiple method is more reusable than the previous version because it works on any stack<t&gt, but it does not look comfortable because it must provide a type parameter for T. However, many times the compiler can infer the correct type parameter by passing the other arguments to the method, which is called type inference (kind inferencing). In the example above, the compiler can assume that the type parameter must be int because the type of the first formal argument is Stack<int&gt, and the following argument types are int. Therefore, you do not have to provide a type parameter when invoking a Pushmultiple method of a generic type:
stack<int> stack = new stack<int> ();
Pushmultiple (Stack, 1, 2, 3, 4);

19.2 Anonymous Methods
The practice approach and other callback methods usually need to be invoked through a dedicated delegate rather than directly. So so far, we can only put a practice and callback code in a specific method, and then explicitly set up a delegate. Instead, the anonymous method (anonymous methods) allows the code associated with a delegate to be "inline (in-line)" To where the delegate is used, and we can easily write the code directly in the delegate instance. In addition to looking comfortable, anonymous methods also share access to function members that are contained in local statements. If you want to achieve this share in the naming method (which distinguishes it from the anonymous method), you need to manually create a secondary class and "elevate (lifting)" Local members to the domain of the class.
The following example shows a simple input from a form that contains a list box, a text box, and a button. The text in the text box is added to the list box when the button is pressed.
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.ITEMS.ADD (TextBox.Text);
}
}
Although the response to the button's Click event has only one statement, the statement must be placed in a separate method with a complete argument list, and you will manually create a EventHandler delegate that references the method. With anonymous methods, the code for event handling becomes 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 a keyword delegate and an optional list of arguments, and the statements are placed in the "{" and "}" qualifiers. The anonymous method in the previous example does not use the arguments provided to the delegate, so you can omit the argument list. To access the parameters, your name method should contain a list of parameters:
Addbutton.click + = Delegate (object sender, EventArgs e) {
MessageBox.Show ((Button) sender). Text);
};
In the example above, an implicit conversion occurred between the anonymous method and the EventHandler delegate type (the type of the Click event). This implicit conversion is possible because the argument list and the return value type of the delegate are compatible with the anonymous method. The exact compatibility rules are as follows:
• When one of the following regulations is true, the argument list of the delegate and the anonymous method are compatible:
O anonymous method has no argument list and delegate has no output (out) parameter.
o The parameter list of an anonymous method exactly matches the delegate parameter on the number, type, and modifier of the parameter.
• When one of the following regulations is true, the return value of the delegate is compatible with the anonymous method:
o The return value type of the delegate is void and the anonymous method does not have a returns statement or it does not have any expressions.
o The return value type of the delegate is not a void but the value of the expression associated with the return statement of the anonymous method can be explicitly converted to the delegate's returned value type.
An implicit conversion of an anonymous type to a delegate type occurs only when both the parameter list and the return value type are compatible.
The following example uses an anonymous method to "inline (In-lian)" a function. Anonymous methods are 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 < a.length. i++) result = f (a);
return result;
}
Static double[] MultiplyAllBy (double[] A, double factor) {
Return to 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 accepted double[] element and returns double[] as the result of a function. In the main method, the second argument passed to the Apply method is an anonymous method that is compatible with the function delegate type. This anonymous method simply returns the square value of each element, so invoking the double[from the Apply method contains 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 and returns. To produce this result, the MultiplyAllBy method invokes the Apply method, passing it an anonymous method that multiplies the parameter x and the factor.
If the scope of a local variable or parameter includes an anonymous method, the variable or parameter is called an external variable (outer variables) of the anonymous method. In the MultiplyAllBy method, a and factor are external variables that are passed to the anonymous method of the Apply method. Typically, the lifetime of a local variable is limited within the block or in the statement associated with it. However, the lifetime of a captured external variable extends to at least the delegate reference to the anonymous method meets the garbage collection criteria.

19.2.1 Method Group Conversion
As described in the previous chapters, an anonymous method can be implicitly converted to a compatible delegate type. C # 2.0 allows the same conversion of a set of methods, that is, the explicit instantiation of a delegate can be omitted at any time. 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 a short form is used, the compiler can automatically infer which delegate type should be instantiated, but the effect is the same as the long form.

19.3 iterators
The foreach statement in C # is used to iterate over an element in an enumerable (enumerable) collection. In order to implement enumerable, a collection must have a GetEnumerator method that returns an enumerator (enumerator) that has no parameters. In general, enumerators are difficult to implement, so simplifying the enumerator's task is significant.
An iterator (iterator) is a block of statements that can produce an ordered sequence of (yields) values. The iterator distinguishes the general statement block by one or more yield statements that appear:
The yield return statement produces the next value for this iteration.
The yield break statement indicates that this iteration is complete.
As long as the return value of a function member 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 types constructed from system.collections.generic.ienumerator<t>.
• The so-called enumerable interfaces refer to System.Collections.IEnumerable and types constructed from system.collections.generic.ienumerable<t>.
Understanding iterators is not a member, but it is important to implement a functional member. A member implemented through an iterator can be overridden or overridden with one or more members that use or do not use iterators.
The following Stack<t> class uses an iterator to implement its GetEnumerator method. The iterator enumerates the elements in the stack in order from the top end.
Using System.Collections.Generic;
public class Stack<t>: ienumerable<t>
{
t[] items;
int count;
public void Push (T data) {...}
Public T Pop () {...}
Public ienumerator<t> GetEnumerator () {
for (int i = count–1 i >= 0; i.) {
yield return items;
}
}
}
The advent of the GetEnumerator method makes stack<t> an enumerable type that allows stack<t> instances to use a foreach statement. The following example presses a value of 0 to 9 into an integer stack, and then uses the Foreach loop to display each value in the order from the top to the end.
Using System;
Class Test
{
static void Main () {
stack<int> stack = new stack<int> ();
for (int i = 0; i < 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 GetEnumerator method of the set to get an enumerator. Only one such parameterless GetEnumerator method can be defined in a collection class, but there are usually many ways to implement enumerations, including using parameters to control enumerations. In these cases, a collection can use iterators to implement properties and methods that can return enumerable interfaces. For example,,stack<t> can introduce two new properties--ienumerable<t> types of TopToBottom and bottomtotop:
Using System.Collections.Generic;
public class Stack<t>: ienumerable<t>
{
t[] items;
int count;
public void Push (T data) {...}
Public T Pop () {...}
Public ienumerator<t> GetEnumerator () {
for (int i = count–1 i >= 0; i.) {
yield return items;
}
}
Public ienumerable<t> TopToBottom {
get {
return this;
}
}
Public ienumerable<t> BottomToTop {
get {
for (int i = 0; i < count; i++) {
yield return items;
}
}
}
}
The get accessor of the TopToBottom property returns only this, because the stack itself is an enumerable type. The BottomToTop property uses the C # iterator to return an enumerable interface. The following example shows how to use these two properties to enumerate the elements in a stack in any order:
Using System;
Class Test
{
static void Main () {
stack<int> stack = new stack<int> ();
for (int i = 0; i < 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 of a foreach statement. The following example passes the result of the calling property to a separate print method. This example also shows an iterator being used as a method body for a Fromtoby method with a parameter:
Using System;
Using System.Collections.Generic;
Class Test
{
static void Print (Ienumerable<int> collection) {
foreach (int i in collection) Console.Write ("{0}", i);
Console.WriteLine ();
}
static ienumerable<int> Fromtoby (int from, int to, int by) {
for (int i = from; I <= to; i + by) {
yield return i;
}
}
static void Main () {
stack<int> stack = new stack<int> ();
for (int i = 0; i < 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
Both generic and non-generic enumerable interfaces have only a single member, a parameterless GetEnumerator method that returns an enumerator interface. An enumerable interface is much like an enumerator factory (enumerator factory). A separate enumerator is generated whenever a GetEnumerator method of a class that implements the enumerable interface is invoked correctly.
Using System;
Using System.Collections.Generic;
Class Test
{
static ienumerable<int> FromTo (int from, int to) {
while (from <= to) yield return from++;
}
static void Main () {
Ienumerable<int> e = FromTo (1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write ("{0,3}", X * y);
}
Console.WriteLine ();
}
}
}
The code above prints a simple multiplication table from 1 to 10. Note the FromTo method only invokes one time to produce an enumerable interface E. and E. GetEnumerator () is called multiple times (through a foreach statement) to produce multiple identical enumerators. These enumerators encapsulate the code specified in the FromTo declaration. Note that the code for the iteration changes the from parameter. However, the enumerator is independent because each enumerator has its own copy of the from parameter and to argument. When implementing an enumerable class and an enumerator class, the transition state (an unstable state) between enumerators is one of the many subtle flaws that must be eliminated. The design of iterators in C # can help eliminate these problems, and you can implement robust enumerable and enumerator classes in a simple, instinctive way.

19.4 Incomplete types
Although it is a good programming practice to maintain all the code of a type in a separate file, sometimes when a class becomes very large, it becomes an unrealistic constraint. Also, programmers often use code generators to generate the initial structure of an application and then modify the resulting code. Unfortunately, the existing fixes will be rewritten when the original code needs to be released again later.
Incomplete types allow classes, structs, and interfaces to be divided into small chunks and stored in different source files to make them easy to develop and maintain. In addition, incomplete types can separate machine-generated code from user-written parts, making it easy to use tools to enforce the resulting code.
To define a type in more than one section, we use a new modifier--partial. The following example implements an incomplete class in two sections. These two parts may be in different source files, for example, the first part may be the machine generated through the database mapping tool, while the second part is created manually:
public partial class Customer
{
private int id;
private string name;
private string address;
Private list<order> 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 sections above are compiled together, the resulting code is as if the class was written in a unit:
public class Customer
{
private int id;
private string name;
private string address;
Private list<order> orders;
Public Customer () {
...
}
public void SubmitOrder (order order) {
Orders. ADD (order);
}
public bool Hasoutstandingorders () {
return orders. Count > 0;
}
}
All parts of an incomplete type must be compiled together in order to merge them during compilation. It is particularly noteworthy that the incomplete type does not allow you to extend the compiled type.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.