New C # features in the. NET Framework 4

Source: Internet
Author: User

The C # programming language has been greatly improved since its first release in 2002, helping programmers to write code that is easier to understand and easier to maintain. This improvement comes from new features that are constantly being added, such as generic types, nullable value types, lambda expressions, iterator methods, partial classes, and a number of other useful language constructs. Also, these changes are often accompanied by support for the Microsoft. NET Framework Library.

C # 4.0 continues this trend of increasing ease-of-use. This product greatly simplifies many common tasks, including generic types, traditional interoperation, and the handling of dynamic object models. This article aims to explore these new features for in-depth investigations. I'll introduce the generic variance first, and then explore the traditional interop features and dynamic Interop features.

Covariance and Contravariance

Covariance and contravariance are best explained by examples, and the best examples are in the framework. In System.Collections.Generic,,ienumerable<t> and ienumerator<t> represent an object containing a sequence of T and an enumerator (or iterator) to traverse the sequence. These interfaces have long assumed a lot of onerous tasks because they allow for the implementation of the Foreach Loop construct. In C # 3.0, they become more prominent because they play an important role in LINQ and LINQ to Objects: they are. NET interfaces that represent sequences.

So if you have a class hierarchy that includes an employee type and the manager type derived from the employee type (after all, the manager is also an employee), what do you think the following code will do?

Ienumerable<manager> ms = Getmanagers ();ienumerable<employee> es = ms;

It seems as if you should be able to treat the Manager sequence as an Employee sequence. However, in C # 3.0, the assignment will fail, and the compiler will prompt you without the appropriate conversion functionality. After all, this version simply does not understand the semantics of ienumerable<t>. This can be any interface, so for any interface Ifoo<t>, why can say ifoo<manager> basically can replace ifoo<employee>?

In C # 4.0, the assignment operation is valid because ienumerable<t> and several other interfaces have changed, and this change is implemented by the new type parameter covariant support in C #.

Ienumerable<t> is more special than any ifoo<t> because, although it is not apparent at first glance, members using type parameter T (GetEnumerator in ienumerable<t> and The current property in ienumerator<t>) actually uses T only where the value is returned. As a result, you can only get the manager from the sequence and never put the manager into it.

By contrast, let's look at list<t>. For the following reasons, replacing list<employee> with list<manager> will be a disaster:

List<manager> ms = Getmanagers (); List<employee> es = ms; Suppose this were possiblees. ADD (New Employeewhoisnotamanager ()); Uh OH

As this code shows, if you think you are looking at List<employee>, then you can insert any employee. But the list that is actually being manipulated is list<manager>, so the employee who inserted the non-Manager must fail. If this operation is allowed, type safety is lost. List<t> cannot be covariant on T.

In C # 4.0, there is a new language feature that allows you to define some types (such as new ienumerable<t>), allowing conversions between these types of parameters as long as there is a certain relationship between the type parameters being processed.. NET Framework Developers Write This feature is used in ienumerable<t>, and their code is similar to the following code (which, of course, has been simplified):

Public interface Ienumerable<out t> {/* ... */}

Note the Out keyword that is defined by the modifier type parameter T. When the compiler encounters this keyword, it marks the T as covariant and checks that the t used in the interface definition is normal (that is, if they are only used in the output location, which is the origin of the Out keyword).

Why is this characteristic called covariant? By drawing arrows, it's easy to see why. For more image, let's use the Manager and Employee types. Since there is an inheritance relationship between the two classes, there is an implicit reference conversion between the Manager and the Employee.

Manager→employee

Now, because of the annotations of T in Ienumerable<out t>, there is an implicit reference conversion from ienumerable<manager> to ienumerable<employee>. This is the purpose of the T annotation:

Ienumerable<manager>→ienumerable<employee>

This is called covariant because the arrows in the two examples point in the same direction. We first define two types of Manager and Employee. These two types are then used to define the new types Ienumerable<manager> and ienumerable<employee>. The new type is converted in the same way as the old type.

When the two transitions are in the opposite direction, they are contravariant. You may have thought that this would happen when you use the type parameter T as input only, then you are right. For example, the System namespace contains an interface named Icomparable<t> that has a method named CompareTo:

Public interface Icomparable<in t> {   bool CompareTo (T);}

If you have Icomparable<employee>, you should be able to think of it as ICOMPARABLE<MANAGER>, because the only action you can do is to add the Employee to the interface. Since the manager is an employee, it should be possible to join the manager, and indeed join the success. This example uses the IN keyword to modify T, and the following scenarios perform correctly:

icomparable<employee> EC = getemployeecomparer ();icomparable<manager> mc = EC;

This is called contravariance, because this time the two arrows are in the opposite direction:

Manager→employee
Icomparable<manager>←icomparable<employee>

So far, it's easy to summarize the language features: You can add an in or out keyword when you define a type parameter, giving you extra free conversions. However, there are some limitations.

First, this approach applies only to generic interfaces and delegates. You cannot declare generic parameters in this manner for a class or struct. One simple reason for this limitation is that the delegate is much like an interface that has only one method, and because of the existence of a field, the class cannot be considered to be some form of interface in any way. You can treat any field of a generic class as both an input and an output, depending on whether you write to it or read it. If these fields involve type parameters, the parameters are neither covariant nor contravariant.

Second, if an interface or delegate has a covariant or contravariant type parameter, the type parameter is a reference type that allows a new transformation to be performed only if the interface is used instead of its definition. For example, because int is a value type, even if it looks like it should work, ienumerator<int> in fact cannot be converted to IEnumerator <object>:

IEnumerator <int> IEnumerator <object>

This behavior occurs because the transformation must preserve the representation of the type. If a conversion of int to object is allowed, it is not possible to invoke the current property of the result because the value type int is not the same as the representation of the object reference on the stack. However, all reference types have the same representation on the stack, so these additional conversions can only be implemented if the type parameter is a reference type.

Most C # developers are likely to happily use this new language feature because some of the types provided by the. NET Framework are used (ienumerable<t>, icomparable<t>, func<t>, Action<t> and so on), they will get more conversion of the framework type, and there will be fewer compiler errors. In fact, as long as the library is designed to contain generic interfaces and delegates, designers can freely use the new in and out type parameters as needed, making it easier for their users to use the libraries they design.

In addition, this feature requires runtime support, but this support has long existed. But because there is no language to use this support, it has been silent several versions. Similarly, the first few versions of C # allow for some limited contravariant conversions. In particular, they allow you to use methods that have compatible return types to generate delegates. In addition, the array type is always covariant. These existing features are quite different from the new features in C # 4.0, which in effect allows you to define your own types so that some type parameters in the type support covariance and contravariance.

Dynamic scheduling

In the context of interoperability features in C # 4.0, we will first describe the most likely changes.

C # now supports dynamic late binding. The language was always strongly typed and will still be the case in version 4.0. Microsoft believes that this makes C # quick and easy to use for all the tasks that. NET programmers give it. Sometimes, however, you need to communicate with systems that are not based on. NET.

Typically, there are at least two ways to accomplish this. The first approach is to import the external model directly into. NET as a proxy. COM Interop is an example of this. Since the first release of the. NET Framework, it has implemented this strategy through a tool called TLBIMP: The tool can create new. NET proxy types that you can use directly in C #.

The linq-to-sql included with C # 3.0 contains a tool called SQLMETAL that can be used to import an existing database into a C # proxy class for use in conjunction with a query. You can also find a tool for importing Windows Management Instrumentation (WMI) classes into C #. Many techniques allow you to write C # (typically with attributes), and then use handwritten code as the basis for external operations to interoperate, including: Linq-to-sql, Windows Communication Foundation (WCF), and serialization.

The second method completely discards the C # type system, and embeds strings and data into the code. You are actually using this approach when you write code to invoke methods of a JScript object or to embed SQL queries in an ADO. Even if you use reflection to defer binding to the runtime (in this case, interoperate with. NET itself), this is actually the way to do it.

The purpose of the dynamic keyword in C # is to deal with the troublesome things that these methods face. Let's give a simple example: reflection. Typically, you need a lot of boilerplate infrastructure code to use reflection, such as:

Object o = GetObject (); Type t = O.gettype (); object result = T.invokemember ("MyMethod",   BindingFlags.InvokeMethod, NULL,   O, New object[ ] {}); int i = Convert.ToInt32 (result);

With the dynamic keyword, it is not necessary to use reflection in this way to invoke the MyMethod method of an object, but rather to tell the compiler that the O is dynamic, deferring all analysis to the runtime. The implementation code is as follows:

Dynamic o = GetObject (); int i = O.mymethod ();

This code works as expected, and it does the same thing with extremely concise code.

If you look at the ScriptObject class that is used to support JScript object manipulation, you may be more aware of the value of this concise C # syntax. The class has a InvokeMember method with many different parameters, whereas in Silverlight, the class has an Invoke method (note the difference in name) with fewer arguments. These two methods are not the same as the methods you need to call the methods of the IronPython or IronRuby objects, and are different from the methods you need to invoke any number of non-C # objects that might need to interact.

In addition to objects from dynamic languages, you will find many data models that are dynamic and supported by different APIs, such as the HTML DOM, the System.Xml Dom, and the XLINQ model for XML. COM objects are usually dynamic, so it can be beneficial to defer some compiler analysis until run time.

In essence, C # 4.0 provides a simple and unified perspective for dynamic operations. To take advantage of this, all you have to do is specify that the given value is dynamic, ensuring that all operations performed on that value are deferred to runtime for analysis.

In C # 4.0, dynamic is a built-in type that is marked with a pseudo-keyword. However, note that this dynamic is different from var. Variables declared with Var are actually strongly typed, but programmers leave it to the compiler to judge. When a programmer uses dynamic, the compiler does not know what type to use, and the programmer leaves this judgment to the runtime to decide.

Dynamic and DLR

The underlying structure that supports these dynamic operations at run time is called the Dynamic Language runtime (DLR). This new. NET Framework 4 Library runs on top of the CLR as any other managed library. It is responsible for reconciling each dynamic operation between the language that initiates the dynamic operation and the object that actually takes place in the dynamic operation. If the dynamic operation is not handled by an object that actually has a dynamic operation, the runtime component of the C # compiler will handle the binding. A simple but incomplete architecture diagram is shown in Figure 1 .

Figure 1 DLR running above the CLR

One interesting thing about dynamic operations, such as dynamic method calls, is that the receiving object has the opportunity at run time to inject itself into the binding, which can completely determine the semantics of any given dynamic operation. For example, let's take a look at the following code:

Dynamic d = new Mydynamicobject ();d. Bar ("Baz", 3, D);

If the definition of Mydynamicobject is as follows, you can imagine what would happen:

Class Mydynamicobject:dynamicobject {public  override bool Tryinvokemember (    invokememberbinder Binder,     Object[] args, out object result) {    Console.WriteLine ("Method: {0}", Binder. Name);    foreach (var arg in args) {      Console.WriteLine ("Argument: {0}", arg);    }    result = Args[0];    return true;  }}

In fact, the code will output:

Method:barargument:bazargument:3argument:mydynamicobject

By declaring D as type dynamic, the code that uses the Mydynamicobject instance effectively cancels the compile-time check of the operation that the D participates in. Using dynamic means "I don't know what this variable will be, so I don't know what methods or properties it has now." Compilers, let them compile, and leave it to the runtime to determine when the object actually exists. "So even if the compiler doesn't know what the Bar call means, it can compile the call correctly." Then, at run time, the object itself will determine what the Bar call will do. This is the way Tryinvokemember knows how to handle it.

Now, let's say you use a Python object instead of Mydynamicobject:

Dynamic d = getpythonobject ();d. Bar ("Baz", 3, D);

If the object is the file listed below, the code is executed correctly, and the output is almost identical:

def bar (*args):  print "Method:", bar.__name__ for  x in args:    print "Argument:", X

In fact, each time the dynamic value is used, the compiler generates some code to initialize and use the DLR CallSite. The CallSite contains all the information needed to bind at run time, including the method name, additional data such as whether to perform the action in the checked context, and information about the parameter and its type.

If you must maintain this code, this code is exactly as ugly as the reflection code, ScriptObject code, or code that contains the XML query, as shown above. This is a requirement for dynamic functionality in C #, but you don't need to write code like this!

When you use the dynamic keyword, your code can be exactly what you expect: Simple method calls, indexer calls, operators (such as +), type conversions, or even composite operators (for example, + = or + +). You can even use dynamic values in statements such as if (d) and foreach (Var x in D). can also pass D && shortcircuited or d?? Shortcircuited and other code support short circuit.

The value of providing a common infrastructure for such operations by the DLR is that you no longer need to process different APIs for each dynamic model used in your code, just one API is enough. And, you don't even need to use it. The C # compiler will use it for you, which allows you to have more time to write code that you really need. The less infrastructure code you have to maintain, the more efficient you will be.

The C # language does not provide a quick way to define dynamic objects. Dynamic features in C # only involve the use of dynamic objects. Take a look at the following code:

Dynamic list = Getdynamiclist ();d ynamic index1 = GetIndex1 ();d ynamic index2 = GetIndex2 (); string s = List[++index1, index2 + 10]. Foo ();

This code can be compiled and contains a lot of dynamic operations. First of all, INDEX1 has a dynamic pre-increment, followed by dynamic addition of INDEX2. A dynamic indexer fetch operation is then called on the list. The result of these operations calls the member Foo again. Finally, the total result of the expression is converted to a string and stored in S. A line of code contains five dynamic operations, each scheduled at run time.

The compile-time type of each dynamic operation is the dynamic itself, so "dynamic" is much like a flow between computations. Even if you do not include dynamic expressions more than once, you may still have a lot of dynamic operations. There are still five dynamic operations in this line of code:

string s = nondynamiclist[++index1, Index2 + 10]. Foo ();

Because two index expressions are dynamic, the index itself is dynamic. And because the result of the index is dynamic, the Foo call is also dynamic. Then, you face the conversion of the dynamic value to string. This, of course, also occurs dynamically, because the object can be a dynamic object that performs a particular calculation when faced with a conversion request.

Note that in the previous example, C # allows an implicit conversion from any dynamic expression to any type. The last conversion to a string is an implicit conversion that does not require an explicit type conversion operation. Similarly, any type can be implicitly converted to a dynamic type.

In this case, dynamic is more like an object, and the similarity between the two is not confined to these aspects. When the compiler emits your assembly and needs to emit a dynamic variable, it does so by using the type object and then tagging the object specifically. In a sense, dynamic is an object alias, but it increases the extra behavior of dynamically parsing operations when you use it.

If you try to convert between two generic types, and the only difference between the two types is dynamic and object, you'll see this effect, which is always done correctly, because an instance of,list<dynamic> at run time is actually a List Examples of <object>:

list<dynamic> ld = new list<object> ();

If you try to override a method that has an object argument at the time of declaration, you can also learn about the similarity between dynamic and object:

Class C {public  override bool Equals (Dynamic obj) {/     * * *   }}

Although it resolves to a decorated object in your assembly, I do like to treat dynamic as a true type because it reminds you that you use most of the actions that any other type can perform, or you can use dynamic. You can use it as a type parameter or as a return value. For example, the following function definition will let you dynamically use the result of a function call without having to save its return value to a dynamic variable:

Public dynamic getdynamicthing () {/   * ... */}

There is also a lot of detail about how dynamic is handled and dispatched, but you can use it even if you don't know the information. The key point is that you can write code that is similar to C #, and if any part of the code you write is dynamic, the compiler leaves it to run-time processing.

For dynamic features, the last topic I'm going to cover is: failure. The compiler cannot report an error because the compiler cannot check whether the dynamic content you are using actually has a method named Foo. Of course, this does not mean that the Foo call will execute correctly at run time. It is possible to execute correctly, but there are many objects that do not have a method named Foo. When your expression is bound to fail at run time, the binder will try to provide you with an exception. To some extent, this exception is similar to the exception provided by the compiler when you do not use dynamic as a starting point.

Please consider the following code:

try {  dynamic d = "This is a string";  D.foo ();} catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e) {  Console.WriteLine (e.message);}

Here, I have a string, and the string is obviously not known as Foo's method. When the line of code that calls Foo executes, the binding fails, and you get runtimebinderexception. The following is the output of the previous program:

' String ' does not contain a definition for ' Foo '

This is the error message that C # programmers want to see.

Named parameters and optional parameters

In another new feature in C #, the method now supports optional parameters with default values, so you can omit them when you call such a method. In the Car class below, you can see an example of it:

Class Car {public  void accelerate (    double speed, int gear = NULL,     bool Inreverse = false) {/     * ... */
   }}

You can call the method as follows:

Car MyCar = new car (); Mycar.accelerate (55);

This method is equivalent to the following code:

Mycar.accelerate (, null, FALSE);

Because the compiler will insert all of the default values that you omit, the two pieces of code are exactly the same.

C # 4.0 also allows you to specify certain parameters by name when calling a method. In this way, you can pass an argument directly to an optional parameter without passing an argument to all arguments before the parameter.

For example, you want to call accelerate in reverse order, but you do not want to specify gear parameters. Then you can call it as follows:

Mycar.accelerate (inreverse:true);

This is a new C # 4.0 syntax that is equivalent to the following code:

Mycar.accelerate (, NULL, true);

In fact, regardless of whether the arguments in the method you invoke are optional, you can use names when passing arguments. For example, both of these calls are allowed, and the two are equivalent:

Console.WriteLine (format: "{0:f}", Arg0:6.02214179e23); Console.WriteLine (ARG0:6.02214179E23, Format: "{0:f}");

If you call a method that has a large number of parameters, you can even use the name as a description in the code to help you remember each specific parameter.

On the surface, the optional parameters and named parameters are not the same as the interop functions. You don't even think about interoperation when you use both of these parameters. However, the motivation to launch these features comes from the Office API. For example, consider Word programming and the simple SaveAs method on the Document interface. This method has 16 parameters and all parameters are optional. In the previous versions of C #, if you wanted to call this method, you would have to write code similar to the following:

Document d = new document (); object filename = "Foo.docx"; object missing = Type.missing;d.saveas (ref filename, ref missing, Ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref Missing, ref missing, ref missing, ref missing, ref missing);

Now, you can write the following code:

Document d = new document ();d. SaveAs (FileName: "Foo.docx");

I think this is a step forward for any programmer who needs to use a similar API. Improving the quality of life of Office programmers is undoubtedly a decisive factor in adding named and optional parameters to the language.

Now, when you write a. NET library and consider adding a method with optional parameters, you face a choice: To add optional parameters, or to introduce overloads as the C # Programmer has done over the years. In the car.accelerate example, the latter decision causes you to have a type similar to the following:

Class Car {public  void accelerate (uint speed) {     accelerate (speed, null, FALSE);   }  public void accelerate (uint speed, int. gear) {     accelerate (speed, gear, false);   }  public void accelerate (uint speed, int gear,     bool inreverse) {/     * ... */   }}

Choosing the right model for the library you write is entirely up to you. Because C # has no optional parameters so far, the. NET Framework (including. NET Framework 4) tends to use overloading. If you decide to mix and match overloads and optional parameters, C # overload resolution has some obvious decisive rules that determine which overload to invoke in any environment.

Indexed properties

In C # 4.0, there are some smaller language features that are only supported when programming against the COM interop API. The Word interop in the previous example is an example.

C # code always has the concept of an indexer, and you can add indexers to a class to effectively overload the [] operator in an instance of the class. This indexer is also known as the default indexer because it does not have a name and does not require a name when invoked. Some COM APIs also have non-default indexers, which cannot be called effectively by using [], but must specify a name. In addition, you can use indexed properties as attributes that accept some additional parameters.

C # 4.0 supports indexed properties on COM interop types. You cannot define a type with indexed attributes in C #, but you can use this type as long as the COM type has indexed properties. For example, if you consider the Range property on an Excel worksheet, the C # code that does this is as follows:

Using Microsoft.office.interop.excel;class program {  static void Main (string[] args) {    application Excel = new Ap Plication ();    Excel. Visible = true;    Worksheet ws =       Excel. Workbooks.Add (). worksheets["Sheet1"];    Range is a indexed property    ws. range["A1", "C3"]. Value = 123;     System.Console.ReadLine ();    Excel. Quit ();}  }

In this example, range["A1", "C3" is not a property named Range and returns the indexed content. It is a Range accessor call, and A1 and C3 are passed. Although Value may not look like an indexed property, it is actually an indexed property! All of its parameters are optional, and because it is an indexed property, you can omit the parameters by not specifying them. Before the language supports indexed properties, you must write the following calling code:

Ws.get_range ("A1", "C3"). Value2 = 123;

Here, the Value2 attribute is added simply because the indexed property Value does not execute correctly in versions prior to C # 4.0.

Omitting the REF keyword at the COM call point

Some COM APIs pass many parameters at the time of writing by reference, even when the implementation does not need to write back these parameters. In the Office suite, Word is an obvious example of what the COM API does.

When you face such a library and you need to pass arguments by reference, you can no longer pass any expressions that are not local or field, which is a big challenge. In the Word SaveAs example, you can see this: just to call the SaveAs method, you must declare the filename of a local call and the missing of a local call, because these two parameters need to be passed by reference.

Document d = new document (); object filename = "Foo.docx"; object missing = Type.missing;d.saveas (ref filename, ref missing, // ...

In the following new C # code, you may notice that I no longer declare the filename local variable:

D.saveas (FileName: "Foo.docx");

This is because of the new omitted ref feature of COM Interop. Now, when calling COM Interop methods, you can pass any parameter by value, rather than by reference. If you pass a parameter by value, the compiler will temporarily create a local variable on your behalf, and then pass the local variable as necessary for you by reference. Of course, if the method changes the parameters, you will not see the effect of the method call. If you want to, pass the arguments by reference.

This should make it clearer to use the API's code like this.

Embedding COM Interop Types

This is more like a C # compiler feature than a C # language feature, but you can now use COM interop assemblies without requiring that the assembly must exist at run time. The goal is to mitigate the burden of deploying COM Interop assemblies with your application.

When COM Interop is introduced in the original version of the. NET Framework, the concept of the primary interop assembly (PIA) is established. This concept was introduced to address the challenge of sharing COM objects between components. If you have different interop assemblies that define an Excel Worksheet, we cannot share these Worksheet between components because they have different. NET types. The PIA solves this problem by only having one time: all clients use it, so. NET types are always matched.

Although PIA is theoretically a good idea, it is proving to be a big hassle in real-world deployments because it has only one copy, and multiple applications may try to install or uninstall it. And because the PIA is usually big, things are more complicated. Office does not deploy them in the default Office installation mode, and users can easily bypass this assembly system by simply creating their own interop assemblies using TLBIMP.

So now, in order to reverse this situation, two things have happened:

    • For two COM interop types that have the same structure and share the same recognition characteristics (name, GUID, and so on), the runtime can intelligently treat it as a. NET type.
    • The C # compiler takes advantage of this by reproducing the interop types directly in your own assembly at compile time, and therefore no longer requires that the interop assembly be present at run time.

Due to space constraints, I have to omit some of the details, but even if you don't know the information, you should be able to use this feature without any obstacles, just like dynamic functionality. You tell the compiler to embed an interop type into Visual Studio for you by setting the embedded Interop Type property on the reference to true.

Because the C # team wants this approach to be

New C # features in the. NET Framework 4

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.