.Net 集合类

       .NET 有超过 21 种内置的集合类型,包括很多常用数据结构的并行访问和泛型版本。大 部分程序只需要组合使用这些类就能满足需求,你应该很少会需要创建自己的集合类。  .NET Framework 中有些集合类只是为了保持向后兼容性,在新的代码中绝不应该再去 使用了。具体包括以下几个。 

ArrayList。 

Hashtable。 

Queue。 

SortedList。 

Stack。 

ListDictionary。 

HybridDictionary。

 

        避免使用上述集合类的原因,就是因为会发生类型转换和装箱操作。上述集合类保存的 都是对 Object 实例的引用,因此你总是得转换为实际的对象类型。  装箱则是更为严重的问题。假设你有一个 Int32 类型的 ArrayList,每个成员都会单独 装箱并保存在内存堆中。为了访问所有 Int32 成员,并不是像对连续存放的内存数组那样 遍历,而是需要对每个引用都进行一次指针间接引用,再访问内存堆(丧失了就近访问可 能性),再进行一次拆箱操作获取内部的数据值。这太可怕了,请换用普通的定长数组或者 泛型集合类吧。  因为有了功能强大的泛型,有些提供字符串功能的集合类曾经在早期.NET 版本中出现 过,但现在已经过时了。比如 NameValueCollection、OrderedDictionary、StringCollection 和 StringDictionary,这些类本身不存在性能问题,但已经没有使用的必要了,除非你使用 的 API 需要用到他们。  最简单的集合类,并且好像是最常用的,就是“卑微”的 Array。因为 Array 很紧凑, 只用到了一块连续存放的内存,所以 Array 非常完美,在需要访问多个成员时,能够由处理 器缓存的就近访问能力提高性能。Array 的访问时间是个常数,复制速度也很快。只是在改 变大小时,需要新分配一个数组并把成员复制到新的数组对象中去。很多更为复杂的数据结 构就是基于 Array 构建的。  选用哪一种集合类取决于很多因素,包括 API 的语义(push/pop、enqueue/dequeue、 Add/Remove 等)、底层存储机制及缓存的就近访问可能性、Add 和 Remove 等成员操作的 速度、同步访问需求。所有因素都会对程序的性能产生巨大的影响。


泛型集合类
泛型集合类包括以下几个。 

Dictionary<TKey, TValue>。 

HashSet<T>。 

LinkedList<T>。 

List<T>。 

Queue<T>。 

SortedDictionary<TKey, TValue>。 

SortedList<TKey, TValue>。 

SortedSet<T>。 

Stack<T>。 

        所有对应的非泛型版本都已过时了,请务必优先选择泛型版本的集合类。这些类不会导 致装箱操作,不会引入类型转换的开销,大部分情况下内存就近访问可能性都会提升(特别 是采用数组实现的 List 类数据结构)

        当然,这些泛型集合类之间也存在巨大的性能差异。比如 Dictionary、SortedDictionary 和 SortedList 都用于保存键/值对,但插入和检索性能就存在很大差异。  

Dictionary 实现为一个哈希表,插入和读取操作的时间复杂度为 O(1)。如果你不熟 悉时间复杂度,请参阅附录 B 关于大 O 表示法的介绍。 

SortedDictionary 实现为二叉树(Binary Search Tree),插入和读取操作的时间复 杂度为 O(log n)。 

SortedList 实现为有序数组。读取操作的时间复杂度为 O(log n),但最坏情况下的 插入时间复杂度为 O(n)。如果插入的成员数值大小是随机的,那就需要频繁地调整 数组大小并移动已有成员。如果能把已经排序的成员全部一次性插入,然后用于快 速检索,那就十分理想了。  在上述 3 个类中,SortedList 使用的是数组,所以内存需求最少。另外两个类所需的 内存随机访问次数要多很多,但能确保较少的平均插入时间。选择哪一种主要取决于程序 的需求。  HashSet 和 SortedSet 的区别类似于 Dictionary 和 SortedDictionary。 

HashSet 使用了哈希表,插入和删除操作的时间复杂度为 O(1)。 

SortedSet 使用了二叉树,插入和删除操作的时间复杂度为 O(log n)。  List、Stack 和 Queue 在内部都使用了数组,因此能为引用提供就近访问可能性,在操 作数据量较大时效率较高。但是在添加大量数据时,需要调整内部数组的大小。为了避免无 谓的数组大小调整,以及随之而来的 CPU 和 GC 开销,如果你事先知道了成员的数量,就 应该把所需内存预先分配好,只要给构造方法传入容量大小或是给 Capacity 属性赋值即可。 List 的插入操作复杂度为 O(1),但删除和检索操作的时间复杂度却为 O(n)。Stack 和 Queue 只能添加和删除集合顶端的成员,因此时间复杂度永远都是 O(1)。  LinkedList 的插入和删除操作时间复杂度都是 O(1),但应该避免存储基元类型,因为会 对每个成员分配一个新的 LinkedListNode 对象,这是一种浪费。


可并发访问的集合类
可并发访问集合类(Concurrent  Collection)的使用。  可并发访问的集合类都位于 System.Collections.Concurrent 命名空间之下,并且都采用 了泛型式的定义。 

ConcurrentBag<T>(Bag 类似于集合,但成员值允许重复) 。 

ConcurrentDictionary<TKey, TValue>。 

ConccurentQueue<T>。 

ConcurrentStack<T>。

        上述类大部分都在内部使用了 Interlocked 或 Monitor 作为同步原语。你可以也应该用 IL 反编译工具查看一下上述类的源代码。  请注意,上述类的插入和删除 API 中都包含了 TryXXX 方法,当因为其他线程正在操作 而发生冲突时,这些方法就会返回失败。比如 ConcurrentStack 就有一个 TryPop 方法,会 返回布尔值表示能否从栈中弹出数据。如果其他线程正在弹出数据,那么当前线程的 TryPop 将会返回 false。  ConcurrentDictionary 有一些方法也值得特别留意一下,你可以调用 TryAdd 在字典中 添加“键”和“值”,或者用 TryUpdate 更新已有的值。  你一般不会留意数据是否已经存在于 ConcurrentDictionary 之中,不想去确定该添加还 是修改数据。有一个 AddOrUpdate 方法可以完成任务,但不能直接给出一项新数据,而是 需要传入两个委托,分别用于添加和修改操作。如果“键”不存在,就会调用第 1 个委托, 传入该“键”,你需要返回其对应的“值”。如果“键”已经存在,就调用第 2 个委托,传入 “键”和已存在的“值”,你需要返回新“值”(可以就是已存在的“值”)。  不论“键”是否存在,AddOrUpdate 方法都会返回新“值”,但重点是你得明白这个新 “值”有可能并非来自当前线程发起的 AddOrUpdate 调用!这些方法是“线程安全”的,但 却不是“原子性”的。有可能其他线程用同一个“键”调用了方法,而第 1 个线程返回了第 2 个线程的返回“值” 。  AddOrUpdate 还有一个不给出委托的重载版本(只需传入“值”)。 

 dict.AddOrUpdate(   

// 要添加的Key   0,    

// 需要根据Key返回字符串的委托   key => key.ToString(),   

// Key已存在时调用的委托,更新已有Value   (key, existingValue) => existingValue);   

dict.AddOrUpdate(   

// 要添加的Key    0,   

// 新添加的Key对应的Value   "0",   

// Key已存在时调用的委托,更新已有Value   (key, existingValue) => existingValue);  

这里为什么不直接传入数据,而是要用委托呢?因为很多时候为给定“键”生成“值” 是一件开销很大的操作,你不希望两个线程同时进行这种操作。委托可以让你使用现有数据, 而不是再去生成一份新的拷贝。不过请注意,委托并不一定只会调用一次。如果你需要保证添加和修改操作的同步性(Synchronization),还需要在委托中自行添加同步代码(集合类 本身未提供实现) 。  与 AddOrUpdate 相关的还有 GetOrAdd 方法,工作方式大致相同。   

string val1 = dict.GetOrAdd(   

// 需要读取的键   0,   

// 如果不存在则生成值的委托   

k => k.ToString());   

string val2 = dict.GetOrAdd(   

// 需要读取的键   0,   

// 不存在则加入的值   "0");  

在使用可并发访问的集合类时,请一定要小心。为了确保安全和效率,可并发访问的集 合类有着特殊的使用要求和执行方式,为了能正确、高效地用好它们,你需要确切地了解使 用的上下文环境。

 其他集合类
.NET 还自带了其他几种专用集合类,但大部分都是为字符串服务或存储 Object 的,因 此略过不谈也没问题。值得一提的只有 BitArray 和 BitVector32。  BitArray 代表了位数组。你可以对数组进行按位赋值并执行布尔比较。如果只需要处理 32 位数据,那就可以使用 BitVector32,速度更快,开销也更低,因为 BitVector32 是个“结 构”(比封装为对象的 Int32 更小) 。

创建自定义集合类型

        偶尔会有这种需求。如果内置类都不适 用,你当然可以根据合适的抽象创建自己的集合类。以下给出了一些通用的创建原则: 

1.不管是否有实际含义,都要实现标准的集合类接口(IEnumerable<T>、Icollection <T>、IList<T>、IDictionary<TKey, TValue>)。 

2.在决定内部数据存储方式时,请考虑一下集合类的使用方式。 

3.如果经常会进行顺序访问,请注意一下引用的就近访问可能性,优先推荐采用数组。  

4.是否需要把访问的同步性添加到集合类本身的实现中?也许可以再创建一个可并发 访问的版本?

5.理解各项操作在运行时的复杂度,包括添加、插入、修改、查找、删除等逻辑。请参 阅附录 B 关于大 O 表示法复杂度的介绍。 

6.实现有实际语义的 API,比如堆栈的弹出(Pop)操作,队列的出队(Dequeue)操作。  

list源码

// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  List
** 
** <OWNER>[....]</OWNER>
**
** Purpose: Implements a generic, dynamically sized list as an 
**          array.
**
** 
===========================================================*/
namespace System.Collections.Generic {

    using System;
    using System.Runtime;
    using System.Runtime.Versioning;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Collections.ObjectModel;
    using System.Security.Permissions;

    // Implements a variable-size List that uses an array of objects to store the
    // elements. A List has a capacity, which is the allocated length
    // of the internal array. As elements are added to a List, the capacity
    // of the List is automatically increased as required by reallocating the
    // internal array.
    // 
    [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))]
    [DebuggerDisplay("Count = {Count}")]
    [Serializable]
    public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
    {
        private const int _defaultCapacity = 4;

        private T[] _items;
        [ContractPublicPropertyName("Count")]
        private int _size;
        private int _version;
        [NonSerialized]
        private Object _syncRoot;
        
        static readonly T[]  _emptyArray = new T[0];        
            
        // Constructs a List. The list is initially empty and has a capacity
        // of zero. Upon adding the first element to the list the capacity is
        // increased to 16, and then increased in multiples of two as required.
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public List() {
            _items = _emptyArray;
        }
    
        // Constructs a List with a given initial capacity. The list is
        // initially empty, but will have room for the given number of elements
        // before any reallocations are required.
        // 
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public List(int capacity) {
            if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            Contract.EndContractBlock();

            if (capacity == 0)
                _items = _emptyArray;
            else
                _items = new T[capacity];
        }
    
        // Constructs a List, copying the contents of the given collection. The
        // size and capacity of the new list will both be equal to the size of the
        // given collection.
        // 
        public List(IEnumerable<T> collection) {
            if (collection==null)
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
            Contract.EndContractBlock();

            ICollection<T> c = collection as ICollection<T>;
            if( c != null) {
                int count = c.Count;
                if (count == 0)
                {
                    _items = _emptyArray;
                }
                else {
                    _items = new T[count];
                    c.CopyTo(_items, 0);
                    _size = count;
                }
            }    
            else {                
                _size = 0;
                _items = _emptyArray;
                // This enumerable could be empty.  Let Add allocate a new array, if needed.
                // Note it will also go to _defaultCapacity first, not 1, then 2, etc.
                
                using(IEnumerator<T> en = collection.GetEnumerator()) {
                    while(en.MoveNext()) {
                        Add(en.Current);                                    
                    }
                }
            }
        }
        
        // Gets and sets the capacity of this list.  The capacity is the size of
        // the internal array used to hold items.  When set, the internal 
        // array of the list is reallocated to the given capacity.
        // 
        public int Capacity {
#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return _items.Length;
            }
            set {
                if (value < _size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
                }
                Contract.EndContractBlock();

                if (value != _items.Length) {
                    if (value > 0) {
                        T[] newItems = new T[value];
                        if (_size > 0) {
                            Array.Copy(_items, 0, newItems, 0, _size);
                        }
                        _items = newItems;
                    }
                    else {
                        _items = _emptyArray;
                    }
                }
            }
        }
            
        // Read-only property describing how many elements are in the List.
        public int Count {
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return _size; 
            }
        }

        bool System.Collections.IList.IsFixedSize {
            get { return false; }
        }

            
        // Is this List read-only?
        bool ICollection<T>.IsReadOnly {
#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            get { return false; }
        }

        bool System.Collections.IList.IsReadOnly {
#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            get { return false; }
        }

        // Is this List synchronized (thread-safe)?
        bool System.Collections.ICollection.IsSynchronized {
            get { return false; }
        }
    
        // Synchronization root for this object.
        Object System.Collections.ICollection.SyncRoot {
            get { 
                if( _syncRoot == null) {
                    System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);    
                }
                return _syncRoot;
            }
        }
        // Sets or Gets the element at the given index.
        // 
        public T this[int index] {
#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            get {
                // Following trick can reduce the range check by one
                if ((uint) index >= (uint)_size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException();
                }
                Contract.EndContractBlock();
                return _items[index]; 
            }

#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            set {
                if ((uint) index >= (uint)_size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException();
                }
                Contract.EndContractBlock();
                _items[index] = value;
                _version++;
            }
        }

        private static bool IsCompatibleObject(object value) {
            // Non-null values are fine.  Only accept nulls if T is a class or Nullable<U>.
            // Note that default(T) is not equal to null for value types except when T is Nullable<U>. 
            return ((value is T) || (value == null && default(T) == null));
        }

        Object System.Collections.IList.this[int index] {
            get {
                return this[index];
            }
            set {
                ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);

                try { 
                    this[index] = (T)value;               
                }
                catch (InvalidCastException) { 
                    ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));            
                }
            }
        }

        // Adds the given object to the end of this list. The size of the list is
        // increased by one. If required, the capacity of the list is doubled
        // before adding the new element.
        //
        public void Add(T item) {
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size++] = item;
            _version++;
        }

        int System.Collections.IList.Add(Object item)
        {
            ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);

            try { 
                Add((T) item);            
            }
            catch (InvalidCastException) { 
                ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));            
            }

            return Count - 1;
        }


        // Adds the elements of the given collection to the end of this list. If
        // required, the capacity of the list is increased to twice the previous
        // capacity or the new size, whichever is larger.
        //
        public void AddRange(IEnumerable<T> collection) {
            Contract.Ensures(Count >= Contract.OldValue(Count));

            InsertRange(_size, collection);
        }

        public ReadOnlyCollection<T> AsReadOnly() {
            Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
            return new ReadOnlyCollection<T>(this);
        }
           
        // Searches a section of the list for a given element using a binary search
        // algorithm. Elements of the list are compared to the search value using
        // the given IComparer interface. If comparer is null, elements of
        // the list are compared to the search value using the IComparable
        // interface, which in that case must be implemented by all elements of the
        // list and the given search value. This method assumes that the given
        // section of the list is already sorted; if this is not the case, the
        // result will be incorrect.
        //
        // The method returns the index of the given value in the list. If the
        // list does not contain the given value, the method returns a negative
        // integer. The bitwise complement operator (~) can be applied to a
        // negative result to produce the index of the first element (if any) that
        // is larger than the given search value. This is also the index at which
        // the search value should be inserted into the list in order for the list
        // to remain sorted.
        // 
        // The method uses the Array.BinarySearch method to perform the
        // search.
        // 
        public int BinarySearch(int index, int count, T item, IComparer<T> comparer) {
            if (index < 0)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            if (count < 0)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            if (_size - index < count)
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
            Contract.Ensures(Contract.Result<int>() <= index + count);
            Contract.EndContractBlock();

            return Array.BinarySearch<T>(_items, index, count, item, comparer);
        }
    
        public int BinarySearch(T item)
        {
            Contract.Ensures(Contract.Result<int>() <= Count);
            return BinarySearch(0, Count, item, null);
        }

        public int BinarySearch(T item, IComparer<T> comparer)
        {
            Contract.Ensures(Contract.Result<int>() <= Count);
            return BinarySearch(0, Count, item, comparer);
        }

    
        // Clears the contents of List.
        public void Clear() {
            if (_size > 0)
            {
                Array.Clear(_items, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references.
                _size = 0;
            }
            _version++;
        }
    
        // Contains returns true if the specified element is in the List.
        // It does a linear, O(n) search.  Equality is determined by calling
        // item.Equals().
        //
        public bool Contains(T item) {
            if ((Object) item == null) {
                for(int i=0; i<_size; i++)
                    if ((Object) _items[i] == null)
                        return true;
                return false;
            }
            else {
                EqualityComparer<T> c = EqualityComparer<T>.Default;
                for(int i=0; i<_size; i++) {
                    if (c.Equals(_items[i], item)) return true;
                }
                return false;
            }
        }

        bool System.Collections.IList.Contains(Object item)
        {
            if(IsCompatibleObject(item)) {            
                return Contains((T) item);                
            }
            return false;
        }

        public List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> converter) {
            if( converter == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter);
            }
            // @


            Contract.EndContractBlock();

            List<TOutput> list = new List<TOutput>(_size);
            for( int i = 0; i< _size; i++) {
                list._items[i] = converter(_items[i]);
            }
            list._size = _size;
            return list;
        }

        // Copies this List into array, which must be of a 
        // compatible array type.  
        //
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public void CopyTo(T[] array) {
            CopyTo(array, 0);
        }

        // Copies this List into array, which must be of a 
        // compatible array type.  
        //
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) {
            if ((array != null) && (array.Rank != 1)) {
                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
            }
            Contract.EndContractBlock();

            try {                
                // Array.Copy will check for NULL.
                Array.Copy(_items, 0, array, arrayIndex, _size);
            }
            catch(ArrayTypeMismatchException){
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
            }
        }
    
        // Copies a section of this list to the given array at the given index.
        // 
        // The method uses the Array.Copy method to copy the elements.
        // 
        public void CopyTo(int index, T[] array, int arrayIndex, int count) {
            if (_size - index < count) {
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
            }
            Contract.EndContractBlock();
            
            // Delegate rest of error checking to Array.Copy.
            Array.Copy(_items, index, array, arrayIndex, count);
        }

        public void CopyTo(T[] array, int arrayIndex) {
            // Delegate rest of error checking to Array.Copy.
            Array.Copy(_items, 0, array, arrayIndex, _size);
        }

        // Ensures that the capacity of this list is at least the given minimum
        // value. If the currect capacity of the list is less than min, the
        // capacity is increased to twice the current capacity or to min,
        // whichever is larger.
        private void EnsureCapacity(int min) {
            if (_items.Length < min) {
                int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
                // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
                if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                if (newCapacity < min) newCapacity = min;
                Capacity = newCapacity;
            }
        }
   
#if FEATURE_LIST_PREDICATES || FEATURE_NETCORE
        public bool Exists(Predicate<T> match) {
            return FindIndex(match) != -1;
        }

        public T Find(Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            for(int i = 0 ; i < _size; i++) {
                if(match(_items[i])) {
                    return _items[i];
                }
            }
            return default(T);
        }
  
        public List<T> FindAll(Predicate<T> match) { 
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            List<T> list = new List<T>(); 
            for(int i = 0 ; i < _size; i++) {
                if(match(_items[i])) {
                    list.Add(_items[i]);
                }
            }
            return list;
        }
  
        public int FindIndex(Predicate<T> match) {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            return FindIndex(0, _size, match);
        }
  
        public int FindIndex(int startIndex, Predicate<T> match) {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < startIndex + Count);
            return FindIndex(startIndex, _size - startIndex, match);
        }
 
        public int FindIndex(int startIndex, int count, Predicate<T> match) {
            if( (uint)startIndex > (uint)_size ) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);                
            }

            if (count < 0 || startIndex > _size - count) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count);
            }

            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < startIndex + count);
            Contract.EndContractBlock();

            int endIndex = startIndex + count;
            for( int i = startIndex; i < endIndex; i++) {
                if( match(_items[i])) return i;
            }
            return -1;
        }
 
        public T FindLast(Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            for(int i = _size - 1 ; i >= 0; i--) {
                if(match(_items[i])) {
                    return _items[i];
                }
            }
            return default(T);
        }

        public int FindLastIndex(Predicate<T> match) {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            return FindLastIndex(_size - 1, _size, match);
        }
   
        public int FindLastIndex(int startIndex, Predicate<T> match) {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() <= startIndex);
            return FindLastIndex(startIndex, startIndex + 1, match);
        }

        public int FindLastIndex(int startIndex, int count, Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() <= startIndex);
            Contract.EndContractBlock();

            if(_size == 0) {
                // Special case for 0 length List
                if( startIndex != -1) {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
                }
            }
            else {
                // Make sure we're not out of range            
                if ( (uint)startIndex >= (uint)_size) {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
                }
            }
            
            // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
            if (count < 0 || startIndex - count + 1 < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count);
            }
                        
            int endIndex = startIndex - count;
            for( int i = startIndex; i > endIndex; i--) {
                if( match(_items[i])) {
                    return i;
                }
            }
            return -1;
        }
#endif // FEATURE_LIST_PREDICATES || FEATURE_NETCORE

        public void ForEach(Action<T> action) {
            if( action == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            int version = _version;

            for(int i = 0 ; i < _size; i++) {
                if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) {
                    break;
                }
                action(_items[i]);
            }

            if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
                ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
        }

        // Returns an enumerator for this list with the given
        // permission for removal of elements. If modifications made to the list 
        // while an enumeration is in progress, the MoveNext and 
        // GetObject methods of the enumerator will throw an exception.
        //
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public Enumerator GetEnumerator() {
            return new Enumerator(this);
        }

        /// <internalonly/>
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif        
        IEnumerator<T> IEnumerable<T>.GetEnumerator() {
            return new Enumerator(this);
        }

#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return new Enumerator(this);
        }

        public List<T> GetRange(int index, int count) {
            if (index < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }

            if (count < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }

            if (_size - index < count) {
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);                
            }
            Contract.Ensures(Contract.Result<List<T>>() != null);
            Contract.EndContractBlock();

            List<T> list = new List<T>(count);
            Array.Copy(_items, index, list._items, 0, count);            
            list._size = count;
            return list;
        }


        // Returns the index of the first occurrence of a given value in a range of
        // this list. The list is searched forwards from beginning to end.
        // The elements of the list are compared to the given value using the
        // Object.Equals method.
        // 
        // This method uses the Array.IndexOf method to perform the
        // search.
        // 
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public int IndexOf(T item) {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            return Array.IndexOf(_items, item, 0, _size);
        }

#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        int System.Collections.IList.IndexOf(Object item)
        {
            if(IsCompatibleObject(item)) {            
                return IndexOf((T)item);
            }
            return -1;
        }

        // Returns the index of the first occurrence of a given value in a range of
        // this list. The list is searched forwards, starting at index
        // index and ending at count number of elements. The
        // elements of the list are compared to the given value using the
        // Object.Equals method.
        // 
        // This method uses the Array.IndexOf method to perform the
        // search.
        // 
        public int IndexOf(T item, int index) {
            if (index > _size)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            Contract.EndContractBlock();
            return Array.IndexOf(_items, item, index, _size - index);
        }

        // Returns the index of the first occurrence of a given value in a range of
        // this list. The list is searched forwards, starting at index
        // index and upto count number of elements. The
        // elements of the list are compared to the given value using the
        // Object.Equals method.
        // 
        // This method uses the Array.IndexOf method to perform the
        // search.
        // 
        public int IndexOf(T item, int index, int count) {
            if (index > _size)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);

            if (count <0 || index > _size - count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count);
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            Contract.EndContractBlock();

            return Array.IndexOf(_items, item, index, count);
        }
    
        // Inserts an element into this list at a given index. The size of the list
        // is increased by one. If required, the capacity of the list is doubled
        // before inserting the new element.
        // 
        public void Insert(int index, T item) {
            // Note that insertions at the end are legal.
            if ((uint) index > (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
            }
            Contract.EndContractBlock();
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            if (index < _size) {
                Array.Copy(_items, index, _items, index + 1, _size - index);
            }
            _items[index] = item;
            _size++;            
            _version++;
        }
    
        void System.Collections.IList.Insert(int index, Object item)
        {
            ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);

            try { 
                Insert(index, (T) item);
            }
            catch (InvalidCastException) { 
                ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));            
            }
        }

        // Inserts the elements of the given collection at a given index. If
        // required, the capacity of the list is increased to twice the previous
        // capacity or the new size, whichever is larger.  Ranges may be added
        // to the end of the list by setting index to the List's size.
        //
        public void InsertRange(int index, IEnumerable<T> collection) {
            if (collection==null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
            }
            
            if ((uint)index > (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
            }
            Contract.EndContractBlock();

            ICollection<T> c = collection as ICollection<T>;
            if( c != null ) {    // if collection is ICollection<T>
                int count = c.Count;
                if (count > 0) {
                    EnsureCapacity(_size + count);
                    if (index < _size) {
                        Array.Copy(_items, index, _items, index + count, _size - index);
                    }
                    
                    // If we're inserting a List into itself, we want to be able to deal with that.
                    if (this == c) {
                        // Copy first part of _items to insert location
                        Array.Copy(_items, 0, _items, index, index);
                        // Copy last part of _items back to inserted location
                        Array.Copy(_items, index+count, _items, index*2, _size-index);
                    }
                    else {
                        T[] itemsToInsert = new T[count];
                        c.CopyTo(itemsToInsert, 0);
                        itemsToInsert.CopyTo(_items, index);                    
                    }
                    _size += count;
                }                
            }
            else {
                using(IEnumerator<T> en = collection.GetEnumerator()) {
                    while(en.MoveNext()) {
                        Insert(index++, en.Current);                                    
                    }                
                }
            }
            _version++;            
        }
    
        // Returns the index of the last occurrence of a given value in a range of
        // this list. The list is searched backwards, starting at the end 
        // and ending at the first element in the list. The elements of the list 
        // are compared to the given value using the Object.Equals method.
        // 
        // This method uses the Array.LastIndexOf method to perform the
        // search.
        // 
        public int LastIndexOf(T item)
        {
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(Contract.Result<int>() < Count);
            if (_size == 0) {  // Special case for empty list
                return -1;
            }
            else {
                return LastIndexOf(item, _size - 1, _size);
            }
        }

        // Returns the index of the last occurrence of a given value in a range of
        // this list. The list is searched backwards, starting at index
        // index and ending at the first element in the list. The 
        // elements of the list are compared to the given value using the 
        // Object.Equals method.
        // 
        // This method uses the Array.LastIndexOf method to perform the
        // search.
        // 
        public int LastIndexOf(T item, int index)
        {
            if (index >= _size)
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(((Count == 0) && (Contract.Result<int>() == -1)) || ((Count > 0) && (Contract.Result<int>() <= index)));
            Contract.EndContractBlock();
            return LastIndexOf(item, index, index + 1);
        }

        // Returns the index of the last occurrence of a given value in a range of
        // this list. The list is searched backwards, starting at index
        // index and upto count elements. The elements of
        // the list are compared to the given value using the Object.Equals
        // method.
        // 
        // This method uses the Array.LastIndexOf method to perform the
        // search.
        // 
        public int LastIndexOf(T item, int index, int count) {
            if ((Count != 0) && (index < 0)) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }

            if ((Count !=0) && (count < 0)) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }
            Contract.Ensures(Contract.Result<int>() >= -1);
            Contract.Ensures(((Count == 0) && (Contract.Result<int>() == -1)) || ((Count > 0) && (Contract.Result<int>() <= index)));
            Contract.EndContractBlock();

            if (_size == 0) {  // Special case for empty list
                return -1;
            }

            if (index >= _size) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
            }

            if (count > index + 1) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
            } 

            return Array.LastIndexOf(_items, item, index, count);
        }
    
        // Removes the element at the given index. The size of the list is
        // decreased by one.
        // 
#if !FEATURE_CORECLR
        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
        public bool Remove(T item) {
            int index = IndexOf(item);
            if (index >= 0) {
                RemoveAt(index);
                return true;
            }

            return false;
        }

        void System.Collections.IList.Remove(Object item)
        {
            if(IsCompatibleObject(item)) {            
                Remove((T) item);
            }
        }

        // This method removes all items which matches the predicate.
        // The complexity is O(n).   
        public int RemoveAll(Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.Ensures(Contract.Result<int>() >= 0);
            Contract.Ensures(Contract.Result<int>() <= Contract.OldValue(Count));
            Contract.EndContractBlock();
    
            int freeIndex = 0;   // the first free slot in items array

            // Find the first item which needs to be removed.
            while( freeIndex < _size && !match(_items[freeIndex])) freeIndex++;            
            if( freeIndex >= _size) return 0;
            
            int current = freeIndex + 1;
            while( current < _size) {
                // Find the first item which needs to be kept.
                while( current < _size && match(_items[current])) current++;            

                if( current < _size) {
                    // copy item to the free slot.
                    _items[freeIndex++] = _items[current++];
                }
            }                       
            
            Array.Clear(_items, freeIndex, _size - freeIndex);
            int result = _size - freeIndex;
            _size = freeIndex;
            _version++;
            return result;
        }

        // Removes the element at the given index. The size of the list is
        // decreased by one.
        // 
        public void RemoveAt(int index) {
            if ((uint)index >= (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }
            Contract.EndContractBlock();
            _size--;
            if (index < _size) {
                Array.Copy(_items, index + 1, _items, index, _size - index);
            }
            _items[_size] = default(T);
            _version++;
        }
    
        // Removes a range of elements from this list.
        // 
        public void RemoveRange(int index, int count) {
            if (index < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }

            if (count < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }
                
            if (_size - index < count)
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
            Contract.EndContractBlock();
    
            if (count > 0) {
                int i = _size;
                _size -= count;
                if (index < _size) {
                    Array.Copy(_items, index + count, _items, index, _size - index);
                }
                Array.Clear(_items, _size, count);
                _version++;
            }
        }
    
        // Reverses the elements in this list.
        public void Reverse() {
            Reverse(0, Count);
        }
    
        // Reverses the elements in a range of this list. Following a call to this
        // method, an element in the range given by index and count
        // which was previously located at index i will now be located at
        // index index + (index + count - i - 1).
        // 
        // This method uses the Array.Reverse method to reverse the
        // elements.
        // 
        public void Reverse(int index, int count) {
            if (index < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }
                
            if (count < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }

            if (_size - index < count)
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
            Contract.EndContractBlock();
            Array.Reverse(_items, index, count);
            _version++;
        }
        
        // Sorts the elements in this list.  Uses the default comparer and 
        // Array.Sort.
        public void Sort()
        {
            Sort(0, Count, null);
        }

        // Sorts the elements in this list.  Uses Array.Sort with the
        // provided comparer.
        public void Sort(IComparer<T> comparer)
        {
            Sort(0, Count, comparer);
        }

        // Sorts the elements in a section of this list. The sort compares the
        // elements to each other using the given IComparer interface. If
        // comparer is null, the elements are compared to each other using
        // the IComparable interface, which in that case must be implemented by all
        // elements of the list.
        // 
        // This method uses the Array.Sort method to sort the elements.
        // 
        public void Sort(int index, int count, IComparer<T> comparer) {
            if (index < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }
            
            if (count < 0) {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            }
                
            if (_size - index < count)
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
            Contract.EndContractBlock();

            Array.Sort<T>(_items, index, count, comparer);
            _version++;
        }

        public void Sort(Comparison<T> comparison) {
            if( comparison == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            if( _size > 0) {
                IComparer<T> comparer = new Array.FunctorComparer<T>(comparison);
                Array.Sort(_items, 0, _size, comparer);
            }
        }

        // ToArray returns a new Object array containing the contents of the List.
        // This requires copying the List, which is an O(n) operation.
        public T[] ToArray() {
            Contract.Ensures(Contract.Result<T[]>() != null);
            Contract.Ensures(Contract.Result<T[]>().Length == Count);

            T[] array = new T[_size];
            Array.Copy(_items, 0, array, 0, _size);
            return array;
        }
    
        // Sets the capacity of this list to the size of the list. This method can
        // be used to minimize a list's memory overhead once it is known that no
        // new elements will be added to the list. To completely clear a list and
        // release all memory referenced by the list, execute the following
        // statements:
        // 
        // list.Clear();
        // list.TrimExcess();
        // 
        public void TrimExcess() {
            int threshold = (int)(((double)_items.Length) * 0.9);             
            if( _size < threshold ) {
                Capacity = _size;                
            }
        }    

#if FEATURE_LIST_PREDICATES || FEATURE_NETCORE
        public bool TrueForAll(Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            Contract.EndContractBlock();

            for(int i = 0 ; i < _size; i++) {
                if( !match(_items[i])) {
                    return false;
                }
            }
            return true;
        } 
#endif // FEATURE_LIST_PREDICATES || FEATURE_NETCORE

        internal static IList<T> Synchronized(List<T> list) {
            return new SynchronizedList(list);
        }

        [Serializable()]
        internal class SynchronizedList : IList<T> {
            private List<T> _list;
            private Object _root;
    
            internal SynchronizedList(List<T> list) {
                _list = list;
                _root = ((System.Collections.ICollection)list).SyncRoot;
            }

            public int Count {
                get {
                    lock (_root) { 
                        return _list.Count; 
                    }
                }
            }

            public bool IsReadOnly {
                get {
                    return ((ICollection<T>)_list).IsReadOnly;
                }
            }

            public void Add(T item) {
                lock (_root) { 
                    _list.Add(item); 
                }
            }

            public void Clear() {
                lock (_root) { 
                    _list.Clear(); 
                }
            }

            public bool Contains(T item) {
                lock (_root) { 
                    return _list.Contains(item);
                }
            }

            public void CopyTo(T[] array, int arrayIndex) {
                lock (_root) { 
                    _list.CopyTo(array, arrayIndex);
                }
            }

            public bool Remove(T item) {
                lock (_root) { 
                    return _list.Remove(item);
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                lock (_root) { 
                    return _list.GetEnumerator();
                }
            }

            IEnumerator<T> IEnumerable<T>.GetEnumerator() {
                lock (_root) { 
                    return ((IEnumerable<T>)_list).GetEnumerator();
                }
            }

            public T this[int index] {
                get {
                    lock(_root) {
                        return _list[index];
                    }
                }
                set {
                    lock(_root) {
                        _list[index] = value;
                    }
                }
            }

            public int IndexOf(T item) {
                lock (_root) {
                    return _list.IndexOf(item);
                }
            }

            public void Insert(int index, T item) {
                lock (_root) {
                    _list.Insert(index, item);
                }
            }

            public void RemoveAt(int index) {
                lock (_root) {
                    _list.RemoveAt(index);
                }
            }
        }

        [Serializable]
        public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
        {
            private List<T> list;
            private int index;
            private int version;
            private T current;

            internal Enumerator(List<T> list) {
                this.list = list;
                index = 0;
                version = list._version;
                current = default(T);
            }

#if !FEATURE_CORECLR
            [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
#endif
            public void Dispose() {
            }

            public bool MoveNext() {

                List<T> localList = list;

                if (version == localList._version && ((uint)index < (uint)localList._size)) 
                {                                                     
                    current = localList._items[index];                    
                    index++;
                    return true;
                }
                return MoveNextRare();
            }

            private bool MoveNextRare()
            {                
                if (version != list._version) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
                }

                index = list._size + 1;
                current = default(T);
                return false;                
            }

            public T Current {
                get {
                    return current;
                }
            }

            Object System.Collections.IEnumerator.Current {
                get {
                    if( index == 0 || index == list._size + 1) {
                         ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
                    }
                    return Current;
                }
            }
    
            void System.Collections.IEnumerator.Reset() {
                if (version != list._version) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
                }
                
                index = 0;
                current = default(T);
            }

        }
    }
}

本文内容出自《编写高性能的.net代码》,源代码来自.net framework源码。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值