C # 2.0: Create an elegant code using anonymous methods, iterative procedures, and local classes

xiaoxiao2021-03-06  18

C # 2.0: Create an elegant code using anonymous methods, iterative procedures, and local classes

Release Date: 11/10/2004

| Update Date: 11/10/2004

Juval low

This article is based on the pre-release version of Microsoft Visual Studio 2005, which previous code name is "whidbey". Any information contained here may vary.

This article discusses:

Traversal collection

Cross-definition

Anonymous method with commissioned

Other C # new features in Visual Studio 2005

This article uses the following technique:

C # and Visual Studio

Can download code here:

C20.exe (164KB)

This page

Iterative Program Iteration Program Implementing Recursive Iterative Local Type Anonymous Method Transfer Parameters to Anonymous Method Anonymous Method Implementing General Anonymous Method Anonymous Method Example Privilege Reasoning Properties and Index Visibility Static Class Global Name Space Limit Inline Inconline Summary

People who are passionate about C # language will like Visual C # 2005. Visual Studio 2005 brings a lot of exciting new features for Visual C # 2005, such as generics, iterative procedures, local classes, and anonymous methods. Although generics is the most common function of people, especially in the middle of C developers familiar with templates, other new features are also important supplements to Microsoft .NET development treasure gallery. Increasing these features and languages ​​increases your overall production efficiency than the first version of C #, allowing you to write a more concise code at a faster speed. Some background knowledge about generics, you should look at the summary column "What is a generic?".

Iterative program

In C # 1.1, you can use the Foreach loop to traverse data structures such as arrays, collections:

String [] cities = {"New York", "Paris", "London"}

FOREACH (String City in Cities)

{

Console.writeLine (City);

}

In fact, you can use any custom data collection in the Foreach cycle as long as the collection type implements the GetEnumerator method that returns the Ienumerator interface. Typically, you need to complete these work by implementing the IEnumerable interface:

Public Interface IEnumerable

{

IEnumerator getenumerator ();

}

Public Interface IEnumerator

{

Object current {get;}

Bool movenext ();

Void reset ();

}

In general, a class for traversing a set of IENUMERABLE is provided as a nested class of a collection type to be traversed. This iterative program type maintains iteration. The nested class is often better because it can access all private members containing classes. Of course, this is an iterative program design mode, which hides the actual implementation details of the underlying data structure, enabling the same client iteration logic on multiple data structures, as shown in FIG.

Figure 1 Iterative programming mode

In addition, since each iterative program remains a separate iterative state, multiple clients can perform separate concurrency. This super conventional iteration can be supported by implementing IEnumerable, such as arrays and queues. The getENUMERATOR method generated in the Foreach cycle is simply obtaining an IEnumerator object, and then it is used for the While loop to spread its MoveNext method and the current attribute traversal collection by continuously calling. If you need to explicitly traverse the collection, you can use IEnumerator directly (do not help to help the Foreach statement). But there are some problems using this method. First, if the collection contains value type, you need to make packages and unboxes to get items because Ienumerator.current returns an object. This will result in potential performance degradation and pressure on the hosted stack. Even if the collection contains the reference type, it still generates adverse results for the type conversion of the object downward. Although most developers are not familiar with this feature, in C # 1.0, it is virtually not necessary to implement IEnumerator or IEnumerable, you can implement iterative program mode for each loop. The compiler will select a strong type of version to avoid mandatory type conversion and packing. As a result, even in version 1.0, it may not result in performance loss.

In order to better clarify this solution and make it easy to implement, Microsoft .NET Framework 2.0 defines a general type of secure IEnumerable and IEnumerator interface in System.Collections.Generics namespace:

Public Interface IEnumerable

{

IEnumerator getenumerator ();

}

Public Interface IEnumerator : idisposable

{

ItemType Current {Get;}

Bool movenext ();

}

In addition to using generics, the new interface is still slightly different from its predecessor. Unlike IEnumerator, IEnumerator is derived from idisposable and there is no reset method. The code in Figure 2 shows a simple city collection that implements IEnumerable , and Figure 3 shows how the compiler uses this interface when the compiler is across the code of the foreach cycle. The implementation of Figure 2 uses a nested class named MyEnumerator, which returns a reference to the collection of the constructor to enumerate. MyEnumerator clearly knows the implementation details of the city collection (one array in this example). The Myenumerator class maintains the current iterative state using the M_Current member variable, which is used as an index of the array.

The second question is also a problem that is more difficult to solve, that is, the implementation of iterative procedures. Although the implementation is quite simple to simple example (as shown in Figure 3), it is very complex, such as a binary tree, which needs to be recursively traversed, and it needs to be recursively traversed, and it needs to maintain iteration status when recursive. In addition, if you need a variety of iterative options, for example, you need to go from head to tail and from tail in a link table, the code of this link table will become bloated due to different iterative procedures. This is the problem to be solved by designing C # 2.0 iteration procedures. By using iterative programs, you can let the C # compiler generate the implementation of IEnumerator. The C # compiler can automatically generate a nested class to maintain iterative status. You can use iterative programs in a general set or a type-specific collection. What you need to do is telling the compiler what is generated in each iteration. Like manually providing iterative procedures, you need to publicize the GetEnumerator method, which is usually disclosed by IENUMERABLE or IENUMERABLE . You can use the new C # Yield Return statement to tell the compiler what is generated. For example, the following code shows how to use a C # iteration program in the city collection instead of manual implementation in Figure 2:

Public Class CityCollection: IEnumerable

{

String [] m_cities = {"New York", "Paris", "London"}

Public IEnumerator getNumerator ()

{

For (int i = 0; i

Yield Return M_cities [i];

}

}

You can also use C # iterative procedures in non-general collection:

Public Class CityCollection: IEnumerable

{

String [] m_cities = {"New York", "Paris", "London"}

Public ienumerator geteNumerator ()

{

For (int i = 0; i

Yield Return M_cities [i];

}

}

In addition, you can use C # iteration programs in a fully general collection, as shown in Figure 4. When using a general set and iterative procedure, the compiler knows the specific type used in the Foreach cycle from the type in the declaration set (String in this example): The specific type of IEnumerable is used:

LinkedList list = new linkedList ();

/ * Some Initialization of List, Then * /

FOREACH (String Item in List)

{

TRACE.WRITELINE (ITEM);

}

This is similar to any other derived derived from the general interface. If you want to stop iteration in some reason, use the Yield BREAK statement. For example, the following iterative programs will only generate values ​​1, 2, and 3:

Public IEnumerator getenumerator ()

{

For (int i = 1; i <5; i )

{

Yield Return I; IF (i> 2)

Yield Break;

}

}

Your collection can easily disclose multiple iterative programs, each iterative program is used to traverse the collection in different ways. For example, to traverse the CityCollection class in reverse, provide the properties of the IEnumerable type named Reverse:

Public Class CityCollection

{

String [] m_cities = {"New York", "Paris", "London"}

Public IEnumerable Reverse

{

get

{

For (int i = m_cities.length-1; i> = 0; I -)

Yield Return M_cities [i];

}

}

}

This allows you to use the Reverse property in the foreach cycle:

CityCollection Collection = New CityCollection ();

Foreach (String City in Collection.Reverse)

{

Trace.writeline (city);

}

There are some limits for where and how to use Yield Return statements. Method or attributes containing Yield Return statements can no longer include other returnos, as this will be incorrectly interrupt. You cannot use the Yield Return statement in an anonymous method, or you cannot put the Yield Return statement in the TRY sentence with the CATCH block (nor can you be placed in a Catch block or FINALLY block.

Back to top

Iterative procedure

The nested classes generated by the compiler maintain iterative state. When the iteration program is first called in the Foreach loop (or in the direct iterative code), the code generated by the compiler will create a new iterative program object with the RESET state (an instance of the nested class). When the MoveNext method of the Iterative program is called each time you cycle, it starts from the place where the Yield Return statement is stopped. As long as the Foreach is executed, the iterative program will maintain its status. However, iterative program objects (and its status) are not maintained between multiple foreach cycles. Therefore, calling Foreach again is safe because you will start a new iterative program object to start a new iteration. That's why IEnumeRable does not define the reset method.

But how is the nested iterative program class implement? And how to manage it? The compiler converts a standard method into a method that can be called multiple times, this method uses a simple state machine to resume execution after the previous Yield Return statement. What you need to do is to use the Yield Return statement to indicate what the compiler is generated and when it is generated. The compiler has sufficient intelligence, which can even connect multiple Yield Return statements to the order they appear:

Public Class CityCollection: IEnumerable

{

Public IEnumerator getNumerator ()

{

Yield Return "New York";

Yield Return "Paris";

Yield Return "London";

}

}

Let's take a look at the genumerator method of this class displayed in the following lines: public class mycollection: ienumerable

{

Public IEnumerator getNumerator ()

{

// Some Iteration Code That Uses Yield Return

}

}

When the compiler encounters such a member with Yield Return statements, it is inserted into a definition of nested classes named GetEnumerator $ __ ienumeratorImpl, as shown in the C # pseudo code in Figure 5. (Remember, all the features discussed here - the name of the class and field generated by the compiler - is changing, and in some cases even thoroughly change. You should not try to use the reflection to get these implementations and It is expected to obtain a consistent result.) Nested class implements the same IENUMERABLE interface returned from class members. The compiler uses an instantiated nested type instead of the code in the class member, assigns a reference to the collections to the member variable of the nested class, similar to the manual implementation shown in FIG. 2. In fact, the nested class is a class that provides the IENUMERATOR.

Back to top

Recurrent iteration

When recursring iteration is performed on a data structure like a binary or any other complicated node, the iterative program really shows its advantages. It is quite difficult to implement an iterative process by recursively iterative, but it will become easier if you use a C # iteration. Consider the binary tree in Figure 6. The complete implementation of this binary tree is part of the source code provided herein. This binary tree stores some items in the node. Each node has a general type T (named Item) value. Each node all contains references to the left node and reference to the right node. Stored in the subtree than the Item, stored in the subtree of the right than the Item value. This tree also provides an Add method, adding a set of open T types using parameter qualifiers:

Public void add (params t [] items;

This tree provides a public property called IENUMERABLE type inorder. Inorder calls the recursive private helper method ScanInorder, passed the root node of the tree to ScanIrder. Scaninorder is defined as follows:

IEnumerable scaninorder (Node root);

It returns the implementation of IEnumerable type iterative program, which is traversed in sequence. One thing to note for Scaninorder is that it is used to access the IENUMERABLE returned from recursive calls by recursing this binary tree. In the in-Order iteration, each node first traverses the subtree on the left, then traversed the value of the node itself, and traverses the subtree on the right. For this situation, do three Yield Return statements. In order to traverse the subtree on the left, Scaninorder uses the Foreach cycle on the IEnumerable returned by the recursive call (which pass the node on the left side of the parameter). Once the Foreach loops returns, all nodes of the left sub-tree have been traveled. Then, scaninorder generates the value of the iteration of the root passes to its node and performs another recursive call in the Foreach loop, which is on the subtree on the right. By using attribute inorder, you can write the following Foreach loop to traverse the entire tree: binarytree Tree = new binarytree ();

Tree.Add (4, 6, 2, 7, 5, 3, 1);

Foreach (int Num in tree.inorder)

{

Trace.writeLine (NUM);

}

// Traces 1, 2, 3, 4, 5, 6, 7

The pre-order and post-order iteration can be implemented by adding other properties. Although the ability to use iteration programs in recursive means is obviously a powerful function, it should be cautious when using it because it may have a serious performance problem. Each time you call Scaninorder, you need to instantiate the compiler generated iteration, so recursive traverses a deep tree may result in a large number of objects after the scene. In the symmetric binary tree, there is approximately N weighted program example, where n is the number of nodes in the tree. At any particular moment, there is approximately log (n) in these objects. In a tree with an appropriate size, many such objects will cause the tree through the 0 generation 0 garbage. That is, the nodes that will be checked by using the stack or queue maintenance, the iterative program can easily traverse the recursive data structure (eg, a tree).

Back to top

Local type

C # 1.1 Requires all code of the class in a file. C # 2.0 allows you to open the definitions and implementations of the class or structure in multiple files. By using the New Partial keyword, you can place a part of the class in a file and place another part in a different file. For example, you can put the following code in file myclass1.cs:

Public Partial Class Myclass

{

Public void method1 ()

{...}

}

In the file myclass2.cs, you can insert the following code:

Public Partial Class Myclass

{

Public void method2 ()

{...}

Public int number;

}

In fact, any particular class can be split into any portion. Local type support can be used for class, structure, and interfaces, but cannot include partial enumeration definitions.

The local type is a very useful feature. Sometimes we need to modify the file generated by the machine, such as the web service client packaging class. However, when re-generating this package class, the modification of the file will be discarded. These changes can be opened in separate files by using local classes. ASP.NET 2.0 uses a local class for the Code-Beside class (evolved from Code-Behind), separately stores the part of the machine generated in the page. The Windows Form uses a local class to store the visual designer output of the initializationComponent method and the member control. By using local types, two or more developers can operate on the same type, while they can check their files from source controls without mutual influence. You can ask yourself, if multiple different parts make a conflict definition of the same class, what kind of consequences? The answer is simple. A class (or a structure) may have two different aspects or properties: Accumulative and non-accumulative. The cumulative aspect is the specification that you can choose to add a part of its part, such as interface derive, attributes, indexers, methods, and member variables.

For example, the following code shows how a part is to add an interface derived and implementation:

Public Partial Class Myclass

{}

Public Partial Class Myclass: iMyinterface

{

Public void method1 ()

{...}

Public void method2 ()

{...}

}

Non-accumulated aspects refers to all parts of a type must be consistent. Whether this type is a class or a structure, type visibility (public or internal) and base classes are non-accumulated aspects. For example, the following code cannot be compiled because all parts of MyClass appear in the base class:

Public class mybase

{}

Public Class SomehalClass

{}

Public Partial Class Myclass: MyBase

{}

Public Partial Class Myclass: MyBase

{}

// Does Not Compile

Public Partial Class Myclass: Some PortherClass

{}

In addition to all parts must define the same non-cumulative part, only one part can rewrite the imaginary method or the abstract method, and only one part can implement interface members.

C # 2.0 is to support local types: When the compiler builds an assembly, it combines the individual parts of the same type of files from multiple files and compiles these parts into a single one with Microsoft Intermediate Language, MSIL. Types of. Which part is not included in the generated MSIL record. As in C # 1.1, MSIL does not contain which file used to define which type of record. It is also worth noting that the local type cannot span the assembly, and one type can refuse to include other parts by ignoring the Partial qualifier in its definition.

Because the compiler is only accumulated, a separate file can contain multiple parts, even multiple parts of the same type (although this is worthy of doubting).

In C #, developers typically be named files based on classes contained in the file, which avoids multiple classes in the same file. When using a local type, I suggest that this file indicates which part of this file contains which type (such as MyClassp1.cs, myclassp2.cs), or other consistent way to indicate the content of the source file on the shape. For example, the Windows Form Design will store a part of the local class of the form in Form1.cs and name the file Form1.Designer.cs. Another disadvantage of the local class is that when you start contacting an unfamiliar code base, each part of your maintenance type may spread throughout the project. In this case, it is recommended that you use the Visual Studio Class View because it can accumulate all parts of all parts to you and allow you to navigate each different part by clicking on its members. The navigation bar also provides this feature.

Back to top

Anonymous method

C # supports delegate for calling one or more methods. Entrusting operators and methods to add or delete the target method, which can also be widely used in events, callback, asynchronous calls, multithreading, etc. throughout the .NET framework. However, just in order to use a delegate, sometimes you have to create a class or method. In this case, there is no need for multiple targets, and the code called usually is relatively short and simple. In C # 2.0, an anonymous method is a new feature that allows you to define anonymous (that is, there is no name) method.

For example, the following is a definition and delegate call for a regular SomeMethod method:

Class SomeClass

{

Delegate void somedlegate ();

Public void invokeMethod ()

{

Somedelegate del = New SomedLegate (someMethod);

Del ();

}

Void someMethod ()

{

Messagebox.show ("Hello");

}

}

You can define and implement this method with an anonymous method:

Class SomeClass

{

Delegate void somedlegate ();

Public void invokeMethod ()

{

Somedlegate del = delegate ()

{

Messagebox.show ("Hello");

}

Del ();

}

}

An anonymous methods are defined as an in-line method rather than a member of any class. In addition, the method properties cannot be applied to an anonymous method, and an anonymous method cannot define a general type or add a general constraint.

You should pay attention to two things that are worthy of attention on anonymous methods: Delegate to reserve the overloaded use of keywords and entrustment assignments. Later, you will see how the compiler implements an anonymous method, and by viewing the code, you will quite clearly understand the type of the compiler must reasonably inrete the type of commissioned, instantiate the new delegate object of the reasoning type, will new The commission is packaged into an anonymous methods and assisted it to the delegate used in the anonymous method definition (DEL in the previous example).

An anonymous methods can be used in any place where the delegate type is required. You can pass anonymous methods to any method as long as the method accepts the appropriate delegate type as a parameter:

Class SomeClass

{

Delegate void somedlegate ();

Public void someMethod ()

{

InvokedLelegate (delegate () {messagebox.show ("hello");});

}

Void InvokedLegate (Somedlegate Del) {

Del ();

}

}

If you need to pass an anonymous method to a method that accepts an abstract delegate parameter, for example:

Void InvokedLegate (Delegate Del);

First, you need to convert an anonymous methods to a specific delegate type.

Below is a specific example of the specific and practical example of parameter delivery, which starts a new thread without explicitly defining a ThreadStart commission or thread method:

Public Class Myclass

{

Public void lauchthread ()

{

Thread workerthread = new thread (delegate ()

{

Messagebox.show ("Hello");

});

Workerthread.start ();

}

}

In the previous example, anonymous method is used as a thread method, which will cause the message box to display from the new thread.

Back to top

Pass the parameters to anonymous methods

When defining an anonymous methods with parameters, the parameter type and name should be defined later after the Delegate keyword, as if it is a general method. The method signature must match the definition of its assignment. When the delegate is invoked, the value of the parameters can be transferred, and the normal delegate call is exactly the same:

Class SomeClass

{

Delegate void Somedelegate (String Str);

Public void invokeMethod ()

{

Somedlegate del = delegate (String STR)

{

Messagebox.show (STR);

}

Del ("Hello");

}

}

If the anonymous method does not have a parameters, you can use a pair of empty brackets after the delegate keyword:

Class SomeClass

{

Delegate void somedlegate ();

Public void invokeMethod ()

{

Somedlegate del = delegate ()

{

Messagebox.show ("Hello");

}

Del ();

}

}

However, if you ignore the Delegate keyword with the back of the knot, you will define a special anonymous method, which can assign any delegate with any signature:

Class SomeClass

{

Delegate void Somedelegate (String Str);

Public void invokeMethod ()

{

Somedlegate del = delegate

{

Messagebox.show ("Hello");

}

Del ("Parameter Is Ignored";

}

}

Obviously, if an anonymous method does not depend on any parameters, and you want to use this method code that is not related to the entrustment signature, you can only use such syntax. Note that when you call the delegate, you still need to provide parameters because the compiler generates anonymous parameters for the anonymous method of the entrusted signature, as if you have written the following code (in C # pseudo code):

Somedlegate del = delegate (String)

{

Messagebox.show ("Hello");

}

In addition, anonymous methods without parameter lists cannot be used with delegates indicating the parameters.

An anonymous methods can use any class member variables, and it can also use any local variables defined within the method range, as if it is its own local variable. Figure 7 shows this. Once you know how to pass parameters for an anonymous method, you can easily define anonymous event processing, as shown in Figure 8. Because = operator only connects a delegated internal call list to another delegated internal call list, you can use = to add an anonymous method. Note that in the case of anonymous event processing, the-= operator cannot be used to delete the event processing method unless the anonymous method is added as a handler, you can do this, you can first store the anonymous method as a delegate, then register it through the event. Entrusted. In this case, the - = operator can be used with the same delegate to cancel the anonymous method as a handler.

Back to top

Anonymous method implementation

The code generated by the compiler for anonymous methods greatly depends on the type of parameters or variables used by anonymous methods. For example, an anonymous method uses local variables therebet (also known as external variable) or use class member variables and method parameters? No matter which case, the compiler generates different MSIL. If an anonymous method does not use external variables (that is, it only uses its own parameters or class members), the compiler adds a private method to this class to give a method a unique name. The name of this method has the following format:

__anonymousmethod $ ()

Like members generated by other compilers, this will change, and most likely to change before the final version is released. The method signature will become the signature of its assignment.

The compiler simply converts an anonymous method to the standard instance of the reasoning delegate type, and the private method generated by the packaging machine:

Somedelegate del = new someDelegate (__ anonymousmethod $ 00000000);

Very interesting is that the private method generated by the machine does not appear in IntelliSense, and it cannot be explicitly called because the dollar symbols in its name are an illegal tag for the C # method (but it is a valid MSIL tag. ).

The situation is more difficult when an anonymous method uses external variables. If so, the compiler will add a private nested class with a unique name with the following format:

__LocalsdisplayClass $

Nested categories have a reference to the "THIS>" including the class, which is a valid MSIL member variable name. Nested classes contain common member variables corresponding to each external variable used by an anonymous method. The compiler adds a common method with a unique name to the nested class definition, the format is as follows:

__anonymousmethod $ ()

Method signing will become a signature assigned commission. The compiler is defined by the code, this code creates an instance of a nested class, and performs the necessary value from the external variable to the member variable of the instance. Finally, the compiler creates a new delegate object to pack the public method of the nested class instance, and then call the delegate to call this method. Figure 9 shows the compiler as a code generated by the compiler as the anonymous method defined in Figure 7.

Back to top

General anonymous method

An anonymous method can use the general parameter type, just like other methods. It can use the general type defined within class, for example:

Class SomeClass {

Delegate Void Somedlegate;

Public Void InvokeMethod (t t)

{

Somedlegate del = delegate (t item) {...}

Del (t);

}

}

Because the commission can define a general parameter, anonymous method can use the general type defined in the delegation layer. You can specify a type for method signs. In this case, the method signature must match the particular type of delegate assigned:

Class SomeClass

{

Delegate Void Somedelegate (T T);

Public void invokeMethod ()

{

Somedelegate DEL = delegate (int Number)

{

Messagebox.show (Number.toString ());

}

Del (3);

}

}

Back to top

Anonymous method example

Although the use of anonymous methods may be like an alternative programming technology, I found it quite useful, because when a delegate is enough, use it to use it without having to create a simple method. Figure 10 shows an actual example of a useful anonymous method - Safelabel Windows Form Control.

The Windows form depends on the basic Win32 message. Therefore, it inherits typical Windows programming requirements: only the thread that creates a window can handle it. In .NET Framework 2.0, calling errors always triggers an exception in a Windows form. Therefore, when the form or control is called in another thread, the call must be encapsulated in the correct belonging. The Windows Form has built-in support, which can be used to get rid of this dilemma, and the method is to implement IsynchronizeInvoke interface with the control base class, which is defined as follows:

Public Interface IsynchronizeInvoke

{

Bool infokeequid {get;}

IASYNCRESULT BeginInvoke (Delegate Method, Object [] ARGS;

Object Endinvoke (IASYNCRESULT RESULT);

Object Invoke (Delegate Method, Object [] ARGS;

}

The Invoke method accepts the delegation of the method in the range of threads and will call the thread from being called to the thread. Because you may not always know if you are doing it in the correct thread, you can make a query by using the invokerequired property, so that you can figure out if you need to call the Invoke method. The problem is that using IsynchronizeInvoke will greatly increase the complexity of the programming model, and therefore the preferred method is often encapsulated with an ISYNCHRONIZEINVOKE interface in the control or form, which will automatically use IsynchronizeInvoke.

For example, in order to replace the Label control of the publication of the TEXT attribute, you can define the Saflabel control derived from the Label, as shown in Figure 10. SAFELabel rewrites the Text property of its base class. In its GET and SET, it checks if INVOKE is required. If this is, it needs to use a delegate to access this property. This implementation only calls the implementation of the base class attribute, but is on the correct thread. Because SafeLabel only defines these methods, they can be called by delegate, they are candidates for an anonymous way. SAFELabel passes such a delegate to package anonymous methods as a security implementation of its Text attribute into the Invoke method. Back to top

Entrusted reasoning

The C # compiler assigns a very important feature that assigns a reasonable type that will be instantiated from anonymous methods. In fact, it also provides another C # 2.0 feature called entrusted reasoning. The entrusted reasoning allows the delegation variable to assign methods directly, without having to use the delegate object to pack it. For example, the following C # 1.1 code:

Class SomeClass

{

Delegate void somedlegate ();

Public void invokeMethod ()

{

Somedelegate del = New SomedLegate (someMethod);

Del ();

}

Void someMethod ()

{...}

}

Now you can write the following code to replace the previous code segment:

Class SomeClass

{

Delegate void somedlegate ();

Public void invokeMethod ()

{

Somedlegate del = SomeMethod;

Del ();

}

Void someMethod ()

{...}

}

When a method name is assigned to the delegate, the compiler first reasoning the type of commission. Then, the compiler is checked whether there is a method according to this name, and its signature matches the delegate type of the reason. Finally, the compiler creates a new object of the reason for the delegate type to package this method and assign it to the delegate. If this type is a specific delegate type (that is, other types other than the abstract type Delegate), the compiler can only reasonine the type of commission. The entrusted reasoning is indeed a very useful feature that makes the code concise and elegant.

I believe that as a convention in C # 2.0, you will use the entrusted reasoning, not the previous entrusted instantification method. For example, the following code illustrates how to start a new thread without explicitly create a ThreadStart delegate:

Public Class Myclass

{

Void threadMethod ()

{...}

Public void lauchthread ()

{

Thread workerthread = new thread (threadMethod);

Workerthread.start ();

}

}

When an asynchronous call is activated and a complete callback method is provided, a pair of entrusted reasoning can be used, as shown in Figure 11. First, specify the method name of asynchronous calls to call a matching commission. Then call BeGinInvoke to provide a complete callback method name instead of the ASYNCCALLBACK type.

Back to top

Properties and index visibility

C # 2.0 allows you to specify different visibility for the GET and SET Accessors of the attribute or indexer. For example, in general, it may be wanted to disclose the GET accessor to public, and disclose the SET Accessor to protected. To this end, the protected visibility qualifier can be added to the SET keyword. Similarly, the SET method of the indexer can be defined as protected (see Figure 12). There are several regulations when using attribute visibility. First, the visibility qualifiers applied to the SET or GET can only be a strict subset of this attribute itself. In other words, if this property is public, you can specify Internal, Protected, Protected Internal, Private. If this property visibility is protected, GET or SET cannot be exposed to public. In addition, visibility can only be specified for GET or SET, and the visibility can be specified for them.

Back to top

Static class

Some classes are only a static method or static member (static class), which is very common. In this case, instantiate the objects of these classes are meaningless. For example, the Monitor class or class factory (for example, the Activator class in .NET Framework 1.1) is a static class. In C # 1.1, you can only provide a private default constructor if you want to prevent the developer's objects. If there is no public constructor, you can't instantiate the object of the class:

Public Class MyclassFactory

{

Private myclassfactory ()

{}

Static public Object createObject ()

{...}

}

However, because the C # compiler still allows you to add instance members (although it may never use them), if you only define a static member in the class, you will decide. C # 2.0 supports static classes by allowing classes to be STITIC:

Public Static Class MyclassFactory

{

Static Public T CreateObject ()

{...}

}

The C # 2.0 compiler does not allow you to add a non-statistial member to a static class, nor allowing you to create an instance of this static class, as if it is an abstract class. In addition, you cannot derive a subclass from a static class. This is like the compiler to add Abstract and Sealed in a static class definition. Note that static classes can be defined without defining a static configuration, and a static constructor can be added.

Back to top

Global namespace qualifier

It is likely to have such a nested namespace, and its name matches some other global namespace. In this case, the C # 1.1 compiler will have problems when parsing the namespace reference. Consider the example:

Namespace myApp

{

Namespace System

{

Class myclass

{

Public void mymethod ()

{

System.Diagnostics.trace.writeline ("IT Works!");

}

}

}

}

In C # 1.1, call the Trace class generate compilation errors (no global namespace qualifiers: :). This error occurs that when the compiler attempts to resolve the reference to the System namespace, it uses the scope directly, this range contains the system namespace but does not contain Diagnostics namespace. C # 2.0 Allows you to use global namespace qualifiers: to indicate that the compiler should search in the global scope. You can apply :: The qualifier to the namespace and type, as shown in Figure 13. Back to top

Inline warning

C # 1.1 Allows special compiler warnings from project settings or by posting command line parameters to the compiler. The problem is that this is a global cancel, so this will cancel some warnings you still need. C # 2.0 Allows the use of #pragma warning instructions to explicitly cancel and restore the compiler warning:

// disable 'Field Never Used' Warning

#pragma Warning Disable 169

Public Class Myclass

{

INT m_Number;

}

#pragma Warning Restore 169

A warning is not encouraged in production code. It is forbidden to warn just to make some analysis, for example, when you try to isolate a problem, or when you design the code and you want to get the appropriate initial structure, you don't have to be perfected. In all other cases, avoid canceling the compiler warning. Note that you cannot override the project settings by programming, which means you can't use the Pragma warning instruction to resume the overall cancellation.

Back to top

summary

Some new features in C # 2.0 mentioned herein are specialized solutions to deal with specific issues while simplifying the overall programming model. If you pay attention to work efficiency and quality, you need to generate as many implementations as possible, reduce the programming tasks of repetitive, and make the final code simpler. The new feature gives you, and I believe that they symbolize the arrival of the C # era, which will make themselves excellent tools serving the .NET professional developers.

JUVal Lowy is a software architect who provides consultation and training in .NET design and transplantation. He is also a Microsoft Regional Director of Silicon Valley. A book he is the latest publishing is Programming .NET Components (O'Reilly, 2003). You can contact JuVal on http://www.idesign.net.

From MSDN Magazine, May 2004.

This magazine can be purchased through newsstands from all over the country or subscribe.

Go to the original English page

转载请注明原文地址:https://www.9cbs.com/read-41349.html

New Post(0)