日期:2014-05-17 浏览次数:20940 次
C#笔记7:集合类
本章概要:
1:为什么要使用泛型集合
2:集合的线程安全
??? 2.1:Syncronized静态方法和SyncRoot实例属性的用法
3:选择集合类
4:位集合
?
???? 针对 .NET Framework 的 2.0 版和更高版本的应用程序应当使用 System.Collections.Generic 命名空间中的泛型集合类,与对应的非泛型类相比,这些类提供了更高的类型安全性和效率。为什么这么说呢,看下面的例子:
// The .NET Framework 1.1 way to create a list: System.Collections.ArrayList list1 = new System.Collections.ArrayList(); list1.Add(3); list1.Add(105); System.Collections.ArrayList list2 = new System.Collections.ArrayList(); list2.Add("It is raining in Redmond."); list2.Add("It is snowing in the mountains.");
?
?
???? 添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。 如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作。 强制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装箱的影响非常明显。
???? 另一个限制是缺少编译时类型检查;因为 ArrayList 会将所有项都强制转换为 Object,所以在编译时无法防止客户端代码执行类似如下的操作:
System.Collections.ArrayList list = new System.Collections.ArrayList(); // Add an integer to the list. list.Add(3); // Add a string to the list. This will compile, but may cause an error later. list.Add("It is raining in Redmond."); int t = 0; // This causes an InvalidCastException to be returned. foreach (int x in list) { t += x; }
?
?
????? ArrayList 和其他相似类真正需要的是:客户端代码基于每个实例指定这些类要使用的具体数据类型的方式。 这样将不再需要向上强制转换为 T:System.Object,同时,也使得编译器可以进行类型检查。 换句话说,ArrayList 需要一个类型参数。 这正是泛型所能提供的。
?
???? .NET Framework 1.0 中的集合,如常用的 ArrayList 和 Hashtable通过 Synchronized 属性(此属性返回与集合有关的线程安全包装)提供某种线程安全性。该包装的工作原理是:对每个添加或移除操作锁定整个集合。因此,每个尝试访问集合的线程必须一直等待,直到轮到它来获取锁。这是无法进行伸缩的,并且对于大型集合而言,将会导致性能显著降低。此外,这一设计并不能完全防止出现争用情况。
???? 在 System.Collections.Generic 命名空间中 .NET Framework 2.0 中引入的集合类。这些集合类包括 List<(Of <(T>)>)、Dictionary<(Of <(TKey, TValue>)>) 等。与 .NET Framework 1.0 类相比,这些类提供的类型安全性和性能会更高。不过,.NET Framework 2.0 集合类不提供任何线程同步;当同时在多个线程上添加或移除项时,用户代码必须提供所有同步。
???? 枚举整个集合本质上不是一个线程安全的过程。若要确保枚举过程中的线程安全性,可以在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步或使用 System.Collections.Concurrent 命名空间中的线程安全集合类之一。System.Collections.Concurrent..::.ConcurrentQueue<(Of <(T>)>) 和 System.Collections.Concurrent..::.ConcurrentStack<(Of <(T>)>) 类在枚举元素之前将为元素拍摄快照,以防止转变成另一个线程上的集合。System.Collections.Concurrent..::.ConcurrentDictionary<(Of <(TKey, TValue>)>) 类不拍摄快照。
System.Collections.Concurrent..::.BlockingCollection<(Of <(T>)>) 类提供一个名为 GetConsumingEnumerable 的枚举器方法,该方法通过在枚举项时将项从集合中移除来转变集合。
???? 那么,说到底,到底如何来实现自己的线程安全呢。可以提供一个static object synObj = new object(); 然后lock(synObj)。
???? 在许多的集合类中,都能看到Syncronized静态方法和SyncRoot实例属性,这两个单词的sync就显而易见的说明了是用来实现同步的,那么,它们内在的机制是什么?
???? Syncronized方法用来创造一个新的对象的线程安全包装,例如:
???? HashTable ht = HashTable.Syncronized(new HashTable());
???? 使用这个方法创造的ht对象保证了在多线程环境下,进行对象的添加、删除和解析的时候,系统自动为它创建锁定区域,这样,省去了手工进行线程安全的设置。
???? 但是,使用这个方法并不能保证枚举的同步,例如,一个线程正在删除或添加集合项,而另一个线程同时进行枚举,这时枚举将会抛出异常。所以,在枚举的时候,你必须明确锁定这个集合。这是,我们要锁定并不是集合类对象本身,我们要锁定的是它的SyncRoot对象,这是为什么呢?
???? 举例来说,一个集合类将数据存放在某些类型的内部数据结构,如果,这个类给予这个外面对这个数据结构的访问权,那么仅仅锁定集合对象是无用的,当然,大部分情况下,返回的SyncRoot就是它本身,但是,还是有一些例外,所以,我们如果我们并不能确定集合对象和SyncRoot之间的关系,那么我们还是锁定SyncRoot为最佳选择;
?????????? lock(ht.SyncRoot)
????????? {
?????????? //你可以安全的对ht进行枚举了
??????