现在的位置: 首页 > 综合 > 正文

Effective C#之Item 49:Prepare for C# 2.0

2014年01月05日 ⁄ 综合 ⁄ 共 12204字 ⁄ 字号 评论关闭
 

Item 49: Prepare for C# 2.0

C#2.0最好准备

C# 2.0, available in 2005, will
have some major new features in the C# language. Some of today's best practices
will change with the tools that will be available in the next release. Although
you might not be using these features just yet, you should prepare for them.

C#2.0,在2005年会可用,它将在C#语言方面有一些主要的新特性。随着下次发布带来的工具,今天的一些最好的实践可能会改变。虽然你可能还没有使用这些特性,但是你需要做好准备。

When Visual Studio .NET 2005 is
released, you will get a new, upgraded C# language. The additions to the
language are sure to make you a more productive developer: You'll be able to
write more reusable code and higher-level constructs in fewer lines of source.
All in all, you'll get more done faster.

VS2005发布时,你将得到新的更新过的C#语言。对该语言的增强,确定会使你成为更具生产力的开发者:你将可以使用更少的源代码就能编写更可复用的代码,更高级别的结构体。总之一句话,你可以更快的完成更多。

C# 2.0 has four major new
features: generics, iterators, anonymous methods, and partial types. The focus
of these new features is to increase your productivity as a C# developer. This
item discusses three of those features and how you should prepare for them now.
Generics will have more impact on how you develop software than any of the
other new features in C#. The generics feature is not specific to C#. To
implement C# generics, Microsoft is extending the CLR and Microsoft
Intermediate Language (MSIL) as well as the C# language. C#, Managed C++, and
VB .NET will be capable of creating generics. J# will be capable of consuming
them.

C#2.04个主要的特性:泛型、迭代、匿名方法和分部类型。这些新特性的关注点就是增强你(作为开发者)的生产力。本条款讨论这些特性中的三个,以及你应该如何现在就做好准备。泛型和C#的其它新特性相比,在你如何开发软件方面,有更重要的影响。泛型特征并不是C#专有的。为了实现C#泛型,微软对CLRMSIL以及C#语言进行了扩展。C#、托管C++VB.NET都会有创建泛型的能力。J#在这方面则会进行加强。

Generics provide "parametric
polymorphism," which is a fancy way of saying you that create a series of
similar classes from a single source. The compiler generates different versions
when you provide a specific type for a generic parameter. You use generics to
build algorithms that are parameterized with respect to the structures they act
upon. You can find great candidates for generics in the .NET Collections
namespace: HashTables, ArrayLists, Queue, and Stack all can store different
object types without affecting their implementation. These collections are such
good candidates that the 2.0 release of the .NET Framework will include the
System.Collections.Generic namespace containing generic counterparts for all
the current collection classes. C# 1.0 stores reference to the System.Object
type. Although the current design is reusable for all types, it has many
deficiencies and is not type-safe. Consider this code:

泛型提供了“参数多态”,这很有趣,也就是说,你可以从一个单独的源创建一系列相似的类。当你为泛型参数提供一个特定类型时,编译器会生成不同的版本。使用泛型构建这样的算法:算法运行在相关的参数化的结构上。在.NETCollections命名空间下,可以找到泛型的很好的候选者:HashTablesArrayListsQueueStack,它们都能存储不同的对象类型,而不影响实现。这些集合都是.NET2.0框架很好的候选者,在System.Collections.Generic命名空间下面将包含所有这些集合类的泛型版本。C#1.0存储System.Object的引用。虽然当前的设计对所有的类型都可用,但是它有很多缺点,并且不是类型安全的。考虑这些代码:

  1. ArrayList myIntList = new ArrayList( );
  2. myIntList.Add(32 );
  3. myIntList.Add(98.6 );
  4. myIntList.Add("Bill Wagner" );

 

This compiles just fine, but it
almost certainly is not the intent. Did you really create a design that calls
for a container that holds totally disparate items? Or were you working around
a limitation in the language? This practice means that when you remove items
from the collection, you must add extra code to determine what kind of objects
were put on the list in the first place. In all cases, you need to cast items
from System.Object to the particular type you placed on the list.

这会编译通过,但是几乎不能表达你的意图。对于包含了完全不同的元素的容器,你确实需要这样的设计么?或者说,你是否在和该语言的限制一起工作呢?这个练习意味着,当你从集合里面移除元素的时候,需要添加额外的代码来决定前面你将什么类型的代码放到了list里面。对于所有这些情况,需要将System.Object类型的元素转换成你放入list的特定类型。

But that's not all. Value types
pay a particular penalty when they are placed in these 1.0-style collections.
Anytime you put a value type in a collection, you must store it in a box. You
pay again to remove the item from the box when you access an element in the
collection. This penalty is small, but with large collections of thousands of
items, it adds up quickly. Generics remove this penalty by generating specific
object code for each value type.

但是还不止这些。当值类型被放在1.0风格的集合中时,是需要付出代价的。无论何时你将值类型放到大集合中时,都需要对其进行装箱。当你访问集合中的一个元素时,需要付出拆箱的代价。代价很小,但是对于有上千个元素的大集合来说,累加效果是很明显的。泛型则除去了为每个值类型生成特定对象的代价。

Those of you familiar with C++
templates will have no trouble working with C# generics because the syntax is
very similar. The inner workings for generics, however, are quite different.
Let's look at one simple example to see how generics work and how they are
implemented. Consider this portion of a list class:

熟悉C++模板的人,在C#泛型方面不会有什么麻烦,因为语法很相似。但是,泛型的内部工作机制是很不同的让我们看一个简单的例子,看看泛型是如何工作和实现的。考虑一个list类的一部份:

  1. public class List
  2. {
  3.   internal class Node
  4.   {
  5.     internal object val;
  6.     internal Node next;
  7.   }
  8.   private Node first;
  9.  
  10.  public void AddHead( object t )
  11.   {
  12.     // ...
  13.   }
  14.  
  15.   public object Head()
  16.   {
  17.     return first.val;
  18.   }
  19.  
  20. }
  21.  

This code stores System.Object
references in its collection. Anytime you use it, you must add casts on the
objects accessed from the collection. But using C# generics, you define the
same class like this:

上面代码在集合里面存储了System.Object引用。任何时候,你使用它时,应该对集合里面被访问的对象进行强制转换。但是使用C#泛型,就可以像这样定义类:

  1. public class List < ItemType >
  2. {
  3.   private class Node < ItemType >
  4.   {
  5.     internal ItemType val;
  6.     internal Node < ItemType > next;
  7.   }
  8.   private Node < ItemType > first;
  9.  
  10.   public void AddHead( ItemType t )
  11.   {
  12.     // ...
  13.   }
  14.  
  15.   public ItemType Head( )
  16.   {
  17.     return first.val;
  18.   }
  19. }

 

You replace object with ItemType,
the parameter type in the class definition. The C# compiler replaces ItemType
with the proper type when you instantiate the list. For example, take a look at
this code:

在类的定义中,对于参数类型,使用ItemType替换了object。当你对list进行初始化的时候,C#编译器将使用合适的类型对ItemType进行替换。例如,看这个代码:

  1. List < int > intList = new List < int >();

 

The MSIL generated specifies that
intList stores integer sand only integers. Generics have several advantages
over the implementations you can create today. For starters, the C# compiler
reports compile-time errors if you attempt anything but an integer in the
collection; today, you need to catch those errors by testing the code at
runtime.

生成的MSIL指定intList存储且只能存储整型。对于你今天创建的实现,泛型有一些优势。对于新手,如果你尝试向集合里存储不是整型的其它任何东西,C#编译器都会报编译时错误;而在现在,你需要在运行时通过检测代码来捕捉这些错误。

In C# 1.0, you pay the boxing and
unboxing penalty whenever you move a value type into or out of a collection
that stores System.Object references. Using generics, the JIT compiler creates
a specific instance of the collection that stores a particular value type; you
don't need to box or unbox the items. But there's more to it. The C# designers
want to avoid the code bloat often associated with C++ templates. To save
space, the JIT compiler generates only one version of the type for all
reference types. This provides a size/speed trade-off whereby value types get a
specific version of each type (avoiding boxing), and reference types share a
single runtime version storing System.Object (avoiding code bloat). The
compiler still reports errors when the wrong reference type is used with these
collections.

C#1.0里面,当你向存储System.Object引用的集合中添加或者移除一个值类型时,都需要付出装箱或者拆箱的代价。使用泛型,JTI编译器就创建了存储特定值类型的集合的特定实例;你不需要对元素进行装箱和拆箱。但是不止这些优点。C#设计者想要避免C++模板里面常常会出现的代码肿胀。为了节省空间,JIT编译器为所有的引用类型只生成该类型的一个版本。这是一个空间和速度的折衷:值类型得到了该类型的特定版本,避免了装箱;引用类型共享一个存储System.Object的单独的运行时版本,避免了代码肿胀。当这些集合里面使用了错误的引用类型时,编译器会报错。

To implement generics, the CLR and
the MSIL language undergo some changes. When you compile a generic class, MSIL
contains placeholders for each parameterized type. Consider these two method
declarations in MSIL:

为了实现泛型,CLRMSIL语言经历了一些修改。当你编译泛型类时,MSIL为每个参数化的类型包含了占位符。考虑在MSIL里面声明的这两个方法:

  1. .method public AddHead (!0 t) {
  2.  }
  3.  
  4. .method public !0 Head () {
  5. }
  6.  

!0 is a placeholder for a type to
be created when a particular instantiation is declared and created. Here's one
possible replacement:

!0就是一个类型的占位符,当一个特定的初始化被声明和创建时,该类型就被创建。这也是一个可能的替换:

  1. .method public AddHead (System.Int32 t) {
  2.  }
  3.  
  4. .method public System.Int32 Head () {
  5. }
  6.  

Similarly, variable instantiations
contain the specific type. The previous declaration for a list of integers
becomes this:

简单来讲,可变化的初始化,包含了指定的类型。前面对于整型list的声明就变成了这样:

  1. .locals (class List<int>)
  2. newobj void List<int>::.ctor ()
  3.  

This illustrates the way the C#
compiler and the JIT compiler work together for generics. The C# compiler
generates MSIL that contains placeholders for each type parameter. The JIT
compiler turns these placeholders into specific types either System.Object for
all reference types, or specific value types for each value type. Each variable
instantiation of a generic type includes type information so the C# compiler
can enforce type safety.

上面展示了C#编译器和JIT编译器共同为泛型工作的方式。C#编译器生成的MSIL为每个类型参数包含了占位符。JIT编译器将这些占位符转换成特定的类型:对于所有的引用类型都是System.Object;对于值类型就是特定的值类型。泛型类型的每个可变化的初始化,包含了类型信息,因此C#编译器保证了类型安全:

Constraint definitions for
generics will have a large impact on how you prepare for generics. Remember that
a specific instantiation of a generic runtime class does not get created until
the CLR loads and creates that instantiation at runtime. To generate MSIL for
all possible instantiations of a generic class, the compiler needs to know the
capabilities of the parameterized type in the generic classes you create. The
C# solution for this problem is constraints. Constraints declare expected
capabilities on the parameterized type. Consider a generic implementation of a
binary tree. Binary trees store objects in sorted order; therefore, a binary
tree can store only types that implement IComparable. You can specify this
requirement using constraints:

泛型的约束定义对你如何为泛型做准备有很大的影响。记住,泛型运行时类的特定实例,只有CLR在运行时加载并创建该实例时,才被创建。为了生成泛型类所有实例的MSIL,编译器需要知道,在你创建的泛型类中,参数化类型的能力。对于该问题的C#解决方案就是约束。约束宣布了参数化类型的能力。考虑二叉树的泛型实现。二叉树存储排好序的对象;因此,二叉树仅能存储实现了IComparable的类型。你可以通过使用约束来指定该要求:

  1. public class BinaryTree < ValType > where ValType : IComparable < ValType >
  2. {
  3. }

Using this definition, any
instantiation of BinaryTree using a class that does not support the IComparable
interface won't compile. You can specify multiple constraints. Suppose that you
want to limit your BinaryTree to objects that support ISerializable. You simply
add more constraints. Notice that interfaces and constraints can be generic
types as well:

使用该定义,任何使用了不支持IComparable接口的类来实例化的BinaryTree,将不能通过编译。你可以指定多重约束。假设,你希望限制BinaryTree来支持ISerializable,那么就可以添加更多的约束。注意,接口和限制都可以是泛型类。

  1. public class BinaryTree < ValType > where ValType : IComparable < ValType > , ValType : ISerializable
  2. {
  3. }

 

You can specify one base class and
any number of interfaces as a set of constraints for each parameterized type.
In addition, you can specify that a type must have a parameterless constructor.

对于每个参数化的类型,可以指定一个基类和任意数量的接口,作为约束集合。另外,可以指定类型必须有无参数的构造函数。

Constraints also provide one more
advantage: The compiler assumes that the objects in your generic class support
any interfaces (or base class methods) specified in the constraint list. In the
absence of any constraints, the compiler assumes only the methods defined in
System.Object. You would need to add casts to use any other method. Whenever
you use a method that is not defined in System.Object, you should document
those requirements in a set of constraints.

约束也提供了另外的优势:编译器会假设泛型类里面的对象支持任何约束列表中的接口(或基类方法)。没有任何限制的话,编译器仅假设你支持System.Object里面定义的方法。可能需要添加任何强制转化来使用任何其它方法。当你使用在System.Object里面没有定义的方法时,应该在约束里面对这些要求进行表述。

Constraints point out yet another
reason to use interfaces liberally (see Item
19
): It will be relatively easy to define constraints if you have defined
your functionality using interfaces.

约束指出了使用合法接口的另一个原因(Item 19):如果你使用接口已经定义了你的功能,那么定义约束是相当简单的。

Iterators are a new syntax to
create a common idiom using much less code. Imagine that you create some
specialized new container class. To support your users, you need to create
methods that support traversing this collection and returning the objects in
the collection.

迭代是使用更少代码的创建通用习惯的新语法。假设你创建一些特性的新的容器类。为了给用户提供支持,你需要创建一些方法,支持遍历集合以及返回集合里面的对象。

Today, you would do this by
creating a class that implements IEnumerator. IEnumerator contains two methods Reset
and MoveNextand one property: Current. In addition, you would add IEnumerable
to the list of implemented interfaces on your collection, and its GetEnumerator
method would return an IEnumerator for your collection. By the time you're
done, you have written an extra class with at least three functions, as well as
some state management and another method in your main class. To illustrate
this, you must write this page of code today to handle list enumeration:

今天,你可以通过创建实现了IEnumerator的类来做到这些。IEnumerator包含两个方法Reset MoveNextand,一个属性:Current。另外,你可以在你的集合里面向实现的接口添加IEnumerable,它的GetEnumerator方法将为你的集合返回一个IEnumerator。到现在为止,和一些状态管理及你的主类里面的另一个方法一起,你至少使用了3种方法来编写了一个额外的类。为了表述这些,你应该编写这样的代码来处理枚举列表:

  1. public class List : IEnumerable
  2. {
  3.   internal class ListEnumerator : IEnumerator
  4.   {
  5.     List theList;
  6.     int pos = -1;
  7.  
  8.     internal ListEnumerator( List l )
  9.     {
  10.       theList = l;
  11.     }
  12.  
  13.     public object Current
  14.     {
  15.       get
  16.       {
  17.         return theList [ pos ];
  18.       }
  19.     }
  20.  
  21.     public bool MoveNext( )
  22.     {
  23.       pos++;
  24.       return pos < theList.Length;
  25.     }
  26.  
  27.     public void Reset( )
  28.     {
  29.       pos = -1;
  30.     }
  31.   }
  32.   public IEnumerator GetEnumerator()
  33.   {
  34.     return new ListEnumerator( this );
  35.   }
  36.  
  37.   // Other methods removed.
  38. }
  39.  

C# 2.0 adds new syntax in the
yield keyword that lets you write these iterators more concisely. Here is the
C# 2.0 version of the previous code:

C#2.0使用yield关键字添加了新的语法,让你更明晰的编写这些迭代器。这是前面代码的C#2.0版本:

  1. public class List
  2. {
  3.   public object iterate()
  4.   {
  5.     int i=0;
  6.     while ( i < theList.Length ( ) )
  7.       yield theList [ i++ ];
  8.   }
  9.  
  10.   // Other methods removed.
  11. }

 

The yield statement lets you
replace roughly 30 lines of code with only 6. This means fewer bugs, less
development time, and less source code to maintain all good th

抱歉!评论已关闭.