FCL(4):: ArrayList & List (2)
ArrayList & List (1) 里讲到:
ArrayList底层用的数据对象就是一组Object的Array,提供了一个List功能的接口.
ArrayList.InsertRange(int index, ICollection c) 和 List
这一篇先讲讲ArrayList的同步类.
以前在做多线程的时候,为了保持线程安全,一般我是这样写的:
后来知道原来是提供了一个同步的ArrayList的.ArrayList提供了一个static方法 public static ArrayList Synchronized(ArrayList list),返回来的是一个同步的ArrayList.往此List里加数据就不用担心多个线程同时操作的问题了.呵呵,可以少写很多重复哦,免得对ArrayList操作时总要lock一下.
不过这个接口感觉有点别扭,需要传入一个ArrayList类型参数.于是乎得这样:
ArrayList syncedList = new ArrayList();
syncedList = ArrayList.Synchronized(syncedList);
要是你想偷懒 ArrayList syncedList = ArrayList.Synchronized(null); 那么我只能很抱歉的告诉你,没戏!等待你的只有ArgumentNullException异常.
对于实现Synchronized或者unSynchronized ArrayList实现,要是我的话,最开始能够想到的就是提供一个Synchronized属性,,ture or false.每次对集合操作的时候判断一下Synchronized属性,如何true的话我就lock一下.这样的想法对于我来说是非常直接,顺其自然的事.然而事实上ArrayLsit根本就没有提供一个这样的属性供我选择.通过提供static的ArrayList.Synchronized方法也能猜测它不是这么干的.
我想这一点如果不反射一下是不会知道的.原来里面定义了个SyncArrayList类,它继承自ArrayList,而且是private.用户根本看不到.SyncArrayList类重写了ArrayList所有会对集合状态做更改的操作.但它其实只是简单地调用一下ArrayList的操作,只不过在操作之前lock一下. 这就是为什么ArrayList.Synchronized()方法需要传入一个ArrayList的参数的引用了.它是对普通ArrayList的封装.
这样的实现好像是体现了什么模式,一时想不起来了.ArrayList只需要关心涉及集合的事就可以了,做好本份的事,不需要考虑同步不同步问题.然后再提供一个SyncArrayList封装类为你解决同步的问题,而其内部实现就是简单地调用ArrayList的方法.也有点 单一责任 的味道.
再看上面的方法: lock (this._root)语句,lock了什么呢,原来是ArrayList.SyncRoot属性,这个干吗用的看名字就能猜到了.唉,想想以前的代码多丑陋,还要new一个object的locker对象,直接:
lock(myList.SyncRoot)
myList.Add(object);
不就好了.所以说扩大知识面,好处大大地有.再看看List<T>,发现已经没有Synchronized()方法了,自己想保持同步就只能自己lock一下SyncRoot.而且很多方法已经不是virtual了.这样更改的理由是什么?自己一时真想不到.
接下来说说搞笑的一个方法. ArrayList.GetRange(int index, int count).印象中自己好像没有调用过这个方法. 以前想要得到集合的一部分数据时就会自己遍历,拷贝.无知惹的祸啊.
为什么说这个方法好笑呢,因为它返回来的是一个Range对象,继承自ArrayList.实现手法跟刚才的SyncArrayList类差不多.那到底为什么我说它好笑呢.看看下面代码先:
执行到最后一行会抛出一个InvalidOperationException异常.为什么呢,好像我们上面代码的调用没什么不妥啊.原因看看Range类是如何实现就知道了.
首先Range类初始化的时候需要传入一个原先的ArrayList实例.Range类所有涉及到对集合状态改变的操作都会额外做点工作,然后调用原来的ArrayList的相应方法. 它做的额外操作包括check一下本身的version与原来的ArrayList的version是否一致,调用完原来ArrayList的方法后,同时将两个对象version + 1.
在我给出的测试代码片段中,当执行完第6行copyList.Add(4)后,list和copyList的版本会增1. 执行完第7行list.Add(5)后list的版本加1了,但copyList不会加,于是在调用第8行copyList.Add(6)时检查到两者版本不一致就抛出了刚才的异常.
Range类由于完全依赖于传入的ArrayList,自己只是对其做了访问范围的限制.实现方式相当于一个"浅拷贝".而为了避免原来ArrayList数据源发生改变影响自己数据的访问,于是每次操作时总要判断两者的版本是不是一致.如果不一致,又怕数据发生错误只好抛出一个InvalidOperationException异常. 而用户在看到这个异常的时候会觉得很莫名,因为这个异常的隐患是在第7行list.Add(5)语句中埋下的,可能离获得异常的位置很远.
这样的实现明显是不合理的,框架设计者对于ArrayList.GetRange()函数应该提供的功能定位是有问题的.
GetRange()操作应该是返回一个ArrayList,此返回的List除了数据是原来ArrayLsit的一部分外,不应该跟原来的ArrayList有任何其它的关系.这种方式相当于"深拷贝", 我仅仅是从你那里拿了一组数据而已,而不应该是对原来ArrayList的封装,对访问范围做下限制就可以了.
所幸的是在List<T>.GetRange()方面里可以看到已经对这个做了修改.我觉得这才是我们想要的GetRange方法.