zoukankan      html  css  js  c++  java
  • Effective C# 原则40:根据需求选择集合(译)

    Effective C# 原则40:根据需求选择集合
    Item 40: Match Your Collection to Your Needs

    “哪种集合是最好的?”答案是:“视情况而定。” 不同的集合有不同的性能,而且在不同的行为上有不同的优化。.Net框架支持很多类似的集合:链表,数组,队列,栈,以及其它的一些集合。C#支持多维的数组,它的性能与一维的数组和锯齿数组都有所不同。.Net框架同样包含了很多特殊的集合,在你创建你自己的集合类之前,请仔细参阅这些集合。你可以发现很多集合很快,因为所有的集合都实现了ICollection接口。在说明文档中列出了所有实现了ICollection接口的集合,你将近有20多个集合类可用。

    为了选择适合你使用的集合,你须要考虑在该集合上使用的操作。为了创建一个有伸缩性的程序,你须要使用一些实现了接口的集合类,这样当你发现某个假设的集合不正确时,你可以用其它不同的集合来代替它(参见原则19)。

    .Net框架有三种不同类型的集合:数组,类似数组的集合,以及基于散列值的集合。数组是最简单的也是一般情况下最快的集合,因此我们就从它开始。这也是你经常要使用到的集合类。

    你的第一选择应该经常是System.Array类,确切的说,应该是一个数组类型的类。 选择数组类的首要原因,也是最重要的原因是数组是类型安全的。所有其它集合都是存储的System.Object引用,直到C#2.0才引入了范型(参见原则49)。当你申明一个数组时,编译器为你的类型创建一个特殊的System.Array派生类。例如:这样创建了申明并创建了一个整型的数组:

    private int [] _numbers = new int[100];

    这个数组存储的是整型,而不是System.object的引用。这很重要,因为你在一个数组上添加,访问,删除值类型时,可以避免装箱与拆箱操作的性能损失(参见原则17)。上面的初始化操作创建了一个一维的数组,里面存放了100个整数。所有被数组占用的内存都被清0了。值类型数组都是0,而引用数组都是null。所有在数组里的元素可以用索引访问:

    int j = _numbers[ 50 ];

    另外,访问数组时还可以用foreach迭代,或者使用枚举器:


    foreach ( int i in _numbers )
      Console.WriteLine( i.ToString( ) );

    // or:
    IEnumerator it = _numbers.GetEnumerator( );
    while( it.MoveNext( ))
    {
      int i = (int) it.Current;
      Console.WriteLine( i.ToString( ) );
    }


    如果准备存储单一次序的对象,你应该使用数组。但实际上你的数据结构往往比这复杂的多。这很快诱使我们回到了C风格上的锯齿数组,那就是一个数组里存储另一个数组。有些时候这确实是你想要的,每个外层元素都是内层的一个数组:

    public class MyClass
    {
      // Declare a jagged array:
      private int[] [] _jagged;

      public MyClass()
      {
        // Create the outer array:
        _jagged = new int[5][];

        // Create each inner array:
        _jagged[0] = new int[5];
        _jagged[1] = new int[10];
        _jagged[2] = new int[12];
        _jagged[3] = new int[7];
        _jagged[4] = new int[23];
      }
    }

    每个内层的一维数组可以有同不的大小。在需要不同大小的数组时可以使用锯齿数据。使用锯齿数组的缺点是在列方面上的访问是底效的。检查一个每行上有三个列的锯齿数组时,每次访问都要做两个检测 。在行0列3上的元素和在行1列3的元素没有任何关系。只有多维数组才可在以列方面上的访问有一定的效率。过去,C和C++程序使用二维或多维数组时是把它们映射到一个一维数组上。对于老式的C和C++程序员,这样的标记应该很清楚:

    double num = MyArray[ i * rowLength + j ];

    这个世界上的其它人可能更喜欢这样:

    double num = MyArray[ i, j ];

    但,C和C++其实并不支持多维数组。C#可以,而且对于多维数组的语法:当你创建一个真实的多维数组时,对于编译器和你来说意义都是很清楚的。创建多维数组时只用在熟悉的一维数组标记上扩展一下就行了:

    private int[ , ] _multi = new int[ 10, 10 ];

    前面的申明创建了一个二维数组,而且是10X10的100个元素。在多维数组上的每一维的长度总是一致的。编译器利用这一特性,可以创建更高效的代码。初始化锯齿数组时须要多次使用初始语句。而在我前面的例子里(译注:指这里:private int[] [] _jagged;),你须要5个语句。更大的数组或者更多维的数组须要更多的初始代码,而且你要手动编写。然而 ,多维数组在初始化时只要指定多少维就行了。此外,多维数组在初始化元素时更有效。以于值类型数组在初始化时是直接在有效的数组索引上包含该值,所有的内容就是0。而引用类型的数组则是null。数组的数组在内层上都包含null引用。

    访问多维数组时比访问锯齿数组要快得多,特殊是在列或者对角线上访问时。编译器在数组的每个维上是使用的指针算法。锯齿数组则要为每个一维数组查找正确的(指针引用)值。

    多维数组可以当成数组在很多情况下使用。假设你创建一个棋盘游戏,你可能须要在一个格子上创建一个64个方格的棋盘:


    private Square[ , ] _theBoard = new Square[ 8, 8 ];

    这个初始化创建了数组来存储方格,假设这些方格是引用类型,这些方格自己还没有创建,而且每个元素还是null。为了初始化每个元素,你还要检测数组上的每一维元素:

    for ( int i = 0; i < _theBoard.GetLength( 0 ); i++ )
      for( int j = 0; j < _theBoard.GetLength( 1 ); j++ )
        _theBoard[ i, j ] = new Square( );

    你你还有更灵活的方法来访问多维数组,你可以给定个别的元素索引来访问该元素:


    Square sq = _theBoard[ 4, 4 ];

    如果你要迭代整个集合,你还可以使用迭代器:

    foreach( Square sq in _theBoard )
      sq.PaintSquare( );

    与锯齿数组相比,你可能要这样写:

    foreach( Square[] row in _theBoard )
      foreach( Square sq in row )
        sq.PaintSquare( );

    锯齿数组里的每一个新的维引用了另一个foreach语句。然而,对于一个多维数组,每一个foreach语句都要产生代码来检测数组每个维上的界线。foreach语句在每个维上生成特殊的代码来迭代数组。foreach循环生成的代码与你这样手写是一样的:

    for ( int i = _theBoard.GetLowerBound( 0 );
      i <= _theBoard.GetUpperBound( 0 ); i++ )
      for( int j = _theBoard.GetLowerBound( 1 );
        j <= _theBoard.GetUpperBound( 1 ); j++ )
        _theBoard[ i, j ].PaintSquare( );

    考虑在内层循环上所有对GetLowerBound 和GetUpperBound 的调用,这看上去是认低效的,但这确实是很高效的结构。JIT编译器对数组类是很了解的,它们会缓存数组界线,而且可以识别内层的迭代界线从而省略它们(参见原则11)。

    数组类的两个主要的不利因素可能会让你考虑使用.Net框架里的其它集合类。第一个影响就是改变数组的大小:数组不能动态的改变大小。如果你须要修改任意数组及任意维数的大小,你必须创建一个新的数组,然后拷贝所有已经存在的元素到新的数组里。改变大小要花时间:因为新的数组要分配,而且所有的元素要拷贝到新的数组里。尽管拷贝和移动在托管堆上并不是像使用C和C++的年代那样开销昂贵,但还是很花时间。更重要的是,它会让数据使用陈旧。考虑下面的代码:

    private string [] _cities = new string[ 100 ];

    public void SetDataSources( )
    {
      myListBox.DataSource = _cities;
    }

    public void AddCity( string CityName )
    {
      String[] tmp = new string[ _cities.Length + 1 ];
      _cities.CopyTo( tmp, 0 );
      tmp[ _cities.Length ] = CityName;

      _cities = tmp; // swap the storage.
    }

    即使在调用AddCity 之后,列表框的数据源还是使用拷贝前的旧数据。你新添加的城市根本没有在列表框中显示。

    ArrayList类是在数组上的一个高度抽象类。ArrayList集合混合了一维数组和链表的语义。你可以在ArrayList使用迭代,而且你可以调整它的大小。ArrayList基本上把所有责任都委托给了它所包含了数组上,这就是说ArrayList类与数组类在性能上很接近。它的最大好处就是在你不清楚你的集合具体要多大时,ArrayList类要比数组类好用得多。ArrayList可以随时扩展和收缩。但你还是要在移动和拷贝元素上有性能损失,但这些算法的代码已经写好了而且经过了测试。因为内部存储的数组已经封装在ArrayList对象内,因此陈旧数据的情况就不存在了:用户的指针是指向ArrayList对象而不是内部的数组。.Net框架里的ArrayList集合有点类似C++中标准库里的向量类。

    队列和栈类在System.Array类上提供了特殊的接口。这些特殊的接口是自定义的先进先出的队列接口和后进先出的栈接口。时刻记住,这些集合的内部存储都是基于数组的。在修改集合的大小时同样会得到性能的损失。
    (译注:对于这样的一些集合,在初始化时都有一个大小的参数,应该尽可能的给定集合的容量大小,这里的大小并不是一次性申明和使用的,而是集合中的元素小于这个大小时,不会有移动和拷贝元素的性能损失。因此合理的选择这些集合的容量大小是很关键的。队列的默认大小是32,而栈的默认大小是10,ArrayList默认是0。而它们的构造函数都有一个重载版本可以指定容量。)

    .Net中的集合没有包含链表(linked list)结构。垃圾回收器的高效使得列表(list)结构在实际使用是占用时间最小,因此也让它成为了最佳选择。如果你确实须要链表的行为,你有两个选择。如果你因为希望经常添加和删除里面的元素而选择了列表,那么使用null引用的字典类。简单的存储关键字,你就可以使用ListDictionary 类了,而且它用键/值对应方式实现了单向链表。或者你可以选择HybridDictionary类,这是一个为小集合使用了ListDictionary的类,而且对于大集合会转化到HashTable上来。这些集合以及很多其它内容都在 System.Collections.Specialized 名字空间里。然而,如果因为用户想控制次序而你想使用列表结构,你可以使用ArrayList 集合。ArrayList 可以在任何位置上执行插入操作,即使它的内部是使用的数组存储方式。

    另外两个类支持基于字典的集合:SortedList 和Hashtable。它们都是包含键/值对应的。SortedList以键进行排序,而Hashtable则没有。Hashtable在给定的键上进行查找时更快,但SortedList提供了基于键的有序迭代。Hashtable 通过键的散列值进行查找。如果散列值是高效的,它的查找时间是一个常量,O(1)。有序列表是使用的折半查找算法来查找关键值。这中一个对数的算法操作 :O(ln n)。

    最后,就是BitArray类。正如它的名字所说的,它存储位数值。BitArray是以整数的数组来存储数据的。每个整型存储一个32位的二进制值。这让BitArray类是压缩的,但它还是会降低性能。BitArray 上的每个get和set操作要在存储的整数上完成位操作,这要查找另外的31个位。BitArray 包含的一些方法在很多值类型上实现了立即的Boolean操作:OR, XOR, AND, 和NOT。这些方法用BitArray做为参数,而且BitArray可以用于快速的多位mask操作,BitArray 是一个为位操作进行优化了的容器;经常使用mask操作时,要存储这些位标记的数据时应该使用它。但不要用它灰替换一般用图和Boolean值数组。

    对于数组类的概念而言,在C#的1.x发布版中,没有一个集合类是强类型的。它们都是存储的对象引用。C#的范型将会在所有这些拓扑上包含新的版本。这会是一个最好的方法来创建类型安全的集合。同时,目前的System.Collections 名字空间中包含了抽象的基类,利用这些基类,你可以在类型不安全的集合上创建你自己的类型安全的接口:CollectionBase 和ReadOnlyCollectionBase 提供了列表和向量结构的基类。DictionaryBase 提供了键/值对应的基类。DictionaryBase类是建立在Hashtable上的,它的性能与Hashtable是一致的。
    (译注:在C#1.x中,我们其实可以合建类型安全的集合,当然这只是利用编译器一些特性,例如:
     public class MyObject
     {
     }

     public class MyObjectCollection : ArrayList
     {
      [ObsoleteAttribute("Please use specify object.",true)]
      public override int Add(object value)
      {
       return base.Add (value);
      }

      public int Add(MyObject value)
      {
       return base.Add (value);
      }

      new public MyObject this[int index]
      {
       get
       {
        return base[index] as MyObject;
       }
       set
       {
        base[index] = value;
       }
      }
     }
    这样就可以简单的验证集合的类型了,当然,本质上还是类型不安全的,这只是一个折衷的办法。
    )

    任何时候,你的类型包含集合时,你可能想要把这些集合在你的类上暴露给你的用户。你有两个方法:使用索引或者使用IEnumerable接口。记住,在这一原则的一开始,我就说明过,你可以直接使用[]标记来访问一个数组里的元素,而且你可以用foreach来迭代数组里的任何元素。

    你可以为你的类创建多维的索引,以C++里你可能须要重载[]操作。而在C#里,你可以创建多维索引:

    public int this [ int x, int y ]
    {
      get
      {
        return ComputeValue( x, y );
      }
    }

    添加对索引器的支持就是说你的类型包含一个集合。这也就是说你应该支持IEnumerable 接口。IEnumerable 提供了标准的机制来迭代所有的集合元素:


    public interface IEnumerable
    {
      IEnumerator GetEnumerator( );
    }

    GetEnumerator 方法返回一个实现了IEnumerator 接口的对象。IEnumerator 接口支持集合的历遍:


    public interface IEnumerator
    {
      object Current
      { get; }

      bool MoveNext( );

      void Reset( );
    }

    另外,在使用IEnumerable 接口时,如果你的类型模型是数组,你应该考虑IList 和ICollection 接口。如果你的类型模型是字典,你应该考虑实现IDictionary 接口。你可以自己创建对这些功能强大的接口的实现,而且你可能要花上更多的几页代码来解释如何实现。但这里有一个简单的解决方案:在你创建自己的特殊集合类时从CollectionBase 或者DictionaryBase 派生你的类。

    让我们回顾一下今天的内容,最好的集合取决于你对操作的实现,以及应用程序对空间和时间的目标要求。在大多数情况下,数组提供了最高效的容器。C#里扩展的多维数组,可以简单的实现多维的结构,而且没有性能的损失。当我们的应用程序须要对元素进行更灵活的添加和移动操作时,使用众多集合中更活越的一个就行了。最后,当你创建自己的集合模型类时,不论什么时候都实现索引器和IEnumerable接口。

    =====================================
       

    Item 40: Match Your Collection to Your Needs
    To the question of "Which collection is best?," the answer is "It depends." Different collections have different performance characteristics and are optimized for different actions. The .NET Framework supports many of the familiar collections: lists, arrays, queue, stack, and others. C# supports multidimensional arrays, which have performance characteristics that differ from either single-dimensional arrays or jagged arrays. The framework also includes many specialized collections; look through those before you build your own. You can find all the collections quickly because all collections implement the ICollection interface. The documentation for ICollection lists all classes that implement that interface. Those 20-odd classes are the collections at your disposal.

    To pick the right collection for your proposed use, you need to consider the actions you'll most often perform on that collection. To produce a resilient program, you'll rely in the interfaces that are implemented by the collection classes so that you can substitute a different collection when you find that your assumptions about the usage of the collection were incorrect (see Item 19).

    The .NET Framework has three different kinds of collections: arrays, arraylike collections, and hash-based containers. Arrays are the simplest and generally the fastest, so let's start there. This is the collection type you'll use most often.

    Your first choice should often be the System.Array classor, more correctly, a type-specific array class. The first and most significant reason for choosing the array class is that arrays are type-safe. All other collection classes store System.Object references, until C# 2.0 introduces generics (see Item 49). When you declare any array, the compiler creates a specific System.Array derivative for the type you specify. For example, this declaration creates an array of integers:

    private int [] _numbers = new int[100];

    The array stores integers, not System.Object. That's significant because you avoid the boxing and unboxing penalty when you add, access, or remove value types from the array (see Item 17). That initialization creates a single-dimensional array with 100 integers stored in it. All the memory occupied by the array has a 0 bit pattern stored in it. Arrays of value types are all 0s, and arrays of reference types are all null. Each item in the array can be accessed by its index:

    int j = _numbers[ 50 ];

    In addition to the array access, you can iterate the array using foreach or an enumerator (see Item 11):

    foreach ( int i in _numbers )
      Console.WriteLine( i.ToString( ) );

    // or:
    IEnumerator it = _numbers.GetEnumerator( );
    while( it.MoveNext( ))
    {
      int i = (int) it.Current;
      Console.WriteLine( i.ToString( ) );
    }

    If you are storing a single sequence of like objects, you should store them in an array. But often, your data structures are more complicated collections. It's tempting to quickly fall back on the C-style jagged array, an array that contains other arrays. Sometimes, this is exactly what you need. Each element in the outer collection is an array along the inner direction:

    public class MyClass
    {
      // Declare a jagged array:
      private int[] [] _jagged;

      public MyClass()
      {
        // Create the outer array:
        _jagged = new int[5][];

        // Create each inner array:
        _jagged[0] = new int[5];
        _jagged[1] = new int[10];
        _jagged[2] = new int[12];
        _jagged[3] = new int[7];
        _jagged[4] = new int[23];
      }
    }

    Each inner single-dimension array can be a different size than the outer arrays. Use jagged arrays when you need to create differently sized arrays of arrays. The drawback to jagged arrays is that a column-wise traversal is inefficient. Examining the third column in each row of a jagged array requires two lookups for each access. There is no relationship between the locations of the element at row 0, column 3 and the element at row 1, column 3. Only multidimensional arrays can perform column-wise traversals more efficiently. Old-time C and C++ programmers made their own two- (or more) dimensional arrays by mapping them onto a single-dimension array. For old-time C and C++ programmers, this notation is clear:

    double num = MyArray[ i * rowLength + j ];

    The rest of the world would prefer this:

    double num = MyArray[ i, j ];

    But C and C++ did not support multidimensional arrays. C# does. Use the multidimensional array syntax: It's clearer to both you and the compiler when you mean to create a true multidimensional structure. You create multidimensional arrays using an extension of the familiar single-dimension array notation:

    private int[ , ] _multi = new int[ 10, 10 ];

    The previous declaration creates a two-dimensional array, a 10x10 array with 100 elements. The length of each dimension in a multidimensional array is always constant. The compiler utilizes this property to generate more efficient initialization code. Initializing the jagged array requires multiple initialization statements. In my simple example earlier, you need five statements. Larger arrays or arrays with more dimensions require more extensive initialization code. You must write code by hand. However, multidimensional arrays with more dimensions merely require more dimension specifiers in the initialization statement. Furthermore, the multidimensional array initializes all array elements more efficiently. Arrays of value types are initialized to contain a value at each valid index in the array. The contents of the value are all 0. Arrays of reference types contain null at each index in the array. Arrays of arrays contain null inner arrays.

    Traversing multidimensional arrays is almost always faster than traversing jagged arrays, especially for by-column or diagonal traversals. The compiler can use pointer arithmetic on any dimension of the array. Jagged arrays require finding each correct value for each single-dimension array.

    Multidimensional arrays can be used like any collection in many ways. Suppose you are building a game based on a checker board. You'd make a board of 64 Squares laid out in a grid:

    private Square[ , ] _theBoard = new Square[ 8, 8 ];

    This initialization creates the array storage for the Squares. Assuming that Square is a reference type, the Squares themselves are not yet created, and each array element is null. To initialize the elements, you must look at each dimension in the array:

    for ( int i = 0; i < _theBoard.GetLength( 0 ); i++ )
      for( int j = 0; j < _theBoard.GetLength( 1 ); j++ )
        _theBoard[ i, j ] = new Square( );

    But you have more flexibility in traversing the elements in a multidimensional array. You can get an individual element using the array indexes:

    Square sq = _theBoard[ 4, 4 ];

    If you need to iterate the entire collection, you can use an iterator:

    foreach( Square sq in _theBoard )
      sq.PaintSquare( );

    Contrast that with what you would write for jagged arrays:

    foreach( Square[] row in _theBoard )
      foreach( Square sq in row )
        sq.PaintSquare( );

    Every new dimension in a jagged array introduces another foreach statement. However, with a multidimensional array, a single foreach statement generates all the code necessary to check the bounds of each dimension and get each element of the array. The foreach statement generates specific code to iterate the array by using each array dimension. The foreach loop generates the same code as if you had written this:

    for ( int i = _theBoard.GetLowerBound( 0 );
      i <= _theBoard.GetUpperBound( 0 ); i++ )
      for( int j = _theBoard.GetLowerBound( 1 );
        j <= _theBoard.GetUpperBound( 1 ); j++ )
        _theBoard[ i, j ].PaintSquare( );

    This looks inefficient, considering all those calls to GetLowerBound and GetUpperBound inside the loop statement, but it's actually the most efficient construct. The JIT compiler knows enough about the array class to cache the boundaries and to recognize that internal bounds checking can be omitted (see Item 11).

    Two major disadvantages to the array class will make you examine the other collection classes in the .NET Framework. The first affects resizing the arrays: Arrays cannot be dynamically resized. If you need to modify the size of any dimension of an array, you must create a new array and copy all the existing elements to it. Resizing takes time: A new array must be allocated, and all the elements must be copied from the existing array into the new array. Although this copying and moving is not as expensive on the managed heap as it was in the C and C++ days, it still costs time. More important, it can result in stale data being used. Consider this code fragment:

    private string [] _cities = new string[ 100 ];

    public void SetDataSources( )
    {
      myListBox.DataSource = _cities;
    }

    public void AddCity( string CityName )
    {
      String[] tmp = new string[ _cities.Length + 1 ];
      _cities.CopyTo( tmp, 0 );
      tmp[ _cities.Length ] = CityName;

      _cities = tmp; // swap the storage.
    }

    Even after AddCity is called, the list box uses the old copy of the _cities array for its data source. Your new city never shows up in the list box.

    The ArrayList class is a higher-level abstraction built on an array. The ArrayList collection mixes the semantics of a single-dimension array with the semantics of a linked list. You can perform inserts on an ArrayList, and you can resize an ArrayList. The ArrayList delegates almost all of its responsibilities to the contained array, which means that the ArrayList class has very similar performance characteristics to the Array class. The main advantage of ArrayList over Array is that ArrayList is easier to use when you don't know exactly how large your collection will be. ArrayList can grow and shrink over time. You still pay the performance penalty of moving and copying items, but the code for those algorithms has already been written and tested. Because the internal storage for the array is encapsulated in the ArrayList object, the problem of stale data does not exist: Clients point to the ArrayList object instead of the internal array. The ArrayList collection is the .NET Framework's version of the C++ Standard Library vector class.

    The Queue and the Stack classes provide specialized interfaces on top of the System.Array class. The specific interfaces for those classes build custom interfaces for the first-in, first-out queue and the last-in, first-out stack. Always remember that these collections are built using a single-dimension array as their internal storage. The same performance penalty applies when you resize them.

    The .NET collections don't contain a linked list structure. The efficiency of the garbage collector minimizes the times when a list structure is really the best choice. If you really need linked list behavior, you have two options. If you are using a list because you expect to add and remove items often, you can use the dictionary classes with null values. Simply store the keys. You can use the ListDictionary class, which implements a single linked list of key/value pairs. Or, you can use the HybridDictionary class, which uses the ListDictionary for small collections and switches to a Hashtable for larger collections. These collections and a host of others are in the System.Collections.Specialized namespace. However, if you want to use a list structure because of a user-controllable order, you can use the ArrayList collection. The ArrayList can perform inserts at any location, even though it uses an array as its internal storage.

    Two other classes support dictionary-based collections: SortedList and Hashtable. Both contain key/value pairs. SortedList orders the keys, whereas Hashtable does not. Hashtable performs searches for a given key faster, but SortedList provides an ordered iteration of the elements by key. Hashtable finds keys using the hash value of the key object. It searches by a constant time operation, O(1), if the hash key is very efficient. The sorted list uses a binary search algorithm to find the keys. This is a logarithmic operation: O(ln n).

    Finally, there is the BitArray class. As its name suggests, this holds bit values. The storage for the BitArray is an array of ints. Each int stores 32 binary values. This makes the BitArray class compact, but it can also decrease performance. Each get or set operation in the BitArray performs bit manipulations on the int value that stores the sought value and 31 other bits. BitArray contains methods that apply Boolean operations to many values at once: OR, XOR, AND, and NOT. These methods take a BitArray as a parameter and can be used to quickly mask multiple bits in the BitArray. The BitArray is the optimized container for bit operations; use it when you are storing a collection of bitflags that are often manipulated using masks. Do not use it as a substitute for a general-purpose array of Boolean values.

    With the exception of the Array class, none of the collection classes in the 1.x release of C# is strongly typed. They all store references to Object. C# generics will contain new versions of all these topologies that are built on generics. That will be the best way to create type-safe collections. In the meantime, the current System.Collections namespace contains abstract base classes that you can use to build your own type-safe interfaces on top of the type-unsafe collections: CollectionBase and ReadOnlyCollectionBase provide base classes for a list or vector structure. DictionaryBase provides a base class for key/value pairs. The DictionaryBase class is built using a Hashtable implementation; its performance characteristics are consistent with the Hashtable.

    Anytime your classes contain collections, you'll likely want to expose that collection to the users of your class. You do this in two ways: with indexers and the IEnumerable interface. Remember that, early in this item, I showed you that you can directly access items in an array using [] notation, and you can iterate the items in the array using foreach.

    You can create multidimensional indexers for your classes. These are analogous to the overloaded operator [] that you could write in C++. As with arrays in C#, you can create multidimensional indexers:

    public int this [ int x, int y ]
    {
      get
      {
        return ComputeValue( x, y );
      }
    }

    Adding indexer support usually means that your type contains a collection. That means you should support the IEnumerable interface. IEnumerable provides a standard mechanism for iterating all the elements in your collection:

    public interface IEnumerable
    {
      IEnumerator GetEnumerator( );
    }

    The GetEnumerator method returns an object that implements the IEnumerator interface. The IEnumerator interface supports traversing a collection:

    public interface IEnumerator
    {
      object Current
      { get; }

      bool MoveNext( );

      void Reset( );
    }

    In addition to the IEnumerable interface, you should consider the IList or ICollection interfaces if your type models an array. If your type models a dictionary, you should consider implementing the IDictionary interface. You could create the implementations for these large interfaces yourself, and I could spend several more pages explaining how. But there is an easier solution: Derive your class from CollectionBase or DictionaryBase when you create your own specialized collections.

    Let's review what we've covered. The best collection depends on the operations you perform and the overall goals of space and speed for your application. In most situations, the Array provides the most efficient container. The addition of multidimensional arrays in C# means that it is easier to model multidimensional structures clearly without sacrificing performance. When your program needs more flexibility in adding and removing items, use one of the more robust collections. Finally, implement indexers and the IEnumerable interface whenever you create a class that models a collection.


     

  • 相关阅读:
    如何优雅的,灵活的把想要展示的数据传给前端
    利用AES算法加密数据
    Swing-布局管理器应用--WIN7上计算器的UI实现
    Swing-GridBagLayout用法-入门
    Swing-setMaximumSize()用法-入门
    Swing-setOpaque()用法-入门
    Swing-setBounds()用法-入门
    Swing-setAlignmentX()用法-入门
    Swing-setBorder()用法-入门
    Swing-布局管理器之GridLayout(网格布局)-入门
  • 原文地址:https://www.cnblogs.com/WuCountry/p/694578.html
Copyright © 2011-2022 走看看