编写高质量代码改善C#程序的157个建议[泛型集合、选择集合、集合的安全]
前言
软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类。不管是数组还是集合类,它们都有各自的优缺点。如何使用好集合是我们在开发过程中必须掌握的技巧。不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行。
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:
建议20、使用泛型集合来替代非泛型集合
建议21、选择正确的集合
建议22、确保集合的线性安全
建议20、使用泛型集合来替代非泛型集合
http://www.cnblogs.com/aehyok/p/3384637.html 这里有一篇文章,是我之前专门来介绍泛型的。我们应尽量的使用泛型集合。因为泛型的确有它的好处:
1、提供了类型安全,在编译期间就可以检查错误
2、更重要的是大部分情况下泛型集合的性能比非泛型集合的性能都高很多。
下面我们来看一段简单的测试性能的代码:
class Program { static int collectionCount = 0; static Stopwatch watch = null; static int testCount = 10000000; static void TestBegin() { GC.Collect(); ////强制对所有代码进行即时垃圾回收 GC.WaitForPendingFinalizers();////挂起线程,执行终结器队列中的终结器(即析构方法) GC.Collect();///再次对所有代码进行垃圾回收,主要包括从终结器队列中出来的对象 collectionCount = GC.CollectionCount(0);///返回在0代中执行的垃圾回收次数 watch = new Stopwatch(); watch.Start(); } static void TestEnd() { watch.Stop(); Console.WriteLine("耗时:{0}",watch.ElapsedMilliseconds.ToString()); Console.WriteLine("垃圾回收次数:{0}", GC.CollectionCount(0) - collectionCount); } static void TestArrayList() { ArrayList arrayList = new ArrayList(); int temp = 0; for (int i = 0; i < testCount; i++) { arrayList.Add(i); temp = (int)arrayList[i]; } arrayList = null; } static void TestGenericList() { List<int> list = new List<int>(); int temp = 0; for (int i = 0; i < testCount; i++) { list.Add(i); temp = list[i]; } list = null; } static void Main(string[] args) { Console.WriteLine("开始测试ArrayList"); TestBegin(); TestArrayList(); TestEnd(); Console.WriteLine("开始测试List<T>"); TestBegin(); TestGenericList(); TestEnd(); Console.ReadLine(); } }
执行结果如下
我上面测试的次数是10000000,可以发现,两者在垃圾回收次数和耗时都差距比较大,所以泛型集合有着非泛型集合无法超越的优势。所以还是尽量在我们的程序中使用泛型集合吧。
建议21、选择正确的集合
http://www.cnblogs.com/aehyok/p/3643928.html这里有一篇我刚写的关于集合的博文,主要是简单介绍了一下关于自己使用比较频繁的几个集合。
如果集合的数目固定并且不涉及转型,使用数组效率高,否则就是使用List<T>。
像使用数组、ArrayList、List<T>、Dictionary<key,value>这些集合的有点就是插入和删除数据效率比较高,缺点就是查找的效率相对来说低一些。
关于队列可以参考http://msdn.microsoft.com/zh-cn/library/System.Collections.Queue(v=vs.80).aspx
关于栈可以参考http://msdn.microsoft.com/zh-cn/library/System.Collections.Stack(v=vs.110).aspx
建议22、确保集合的线性安全
建议18中提到,foreach循环不能代替for循环的一个原因是在迭代过程中对集合本身进行了增删操作。将此场景移植到多线程场景中,就是本建议要阐述的重点:确保集合的线程安全。集合线程安全是指在多个线程上添加活删除元素时,线程之间必须保持同步。
下面我们来通过实例来更详细的查看一下,先简单定义一个实体类
public class Person { public string Name { get; set; } public int Age { get; set; } }
static List<Person> list = new List<Person>() { new Person(){ Name="aehyok",Age=25}, new Person(){Name="Kris",Age=23}, new Person(){Name="Leo",Age=26} }; static AutoResetEvent autoSet = new AutoResetEvent(false); static void Main(string[] args) { Thread t1 = new Thread(() => { ///阻止当前线程 autoSet.WaitOne(); foreach (var item in list) { Console.WriteLine("t1:"+item.Name); Thread.Sleep(1000); } }); t1.Start(); Thread t2 = new Thread(() => { ///通知t1可以执行代码 autoSet.Set(); Thread.Sleep(1000); list.RemoveAt(2); }); t2.Start(); Console.ReadLine(); }
再来简单分析一下这段代码,其实就是闲定义了一个List集合,然后又定义了一个 AutoRestEvent的实例,用于控制线程的。
接下来在Main函数中定义了两个线程,在线程一中将线程一暂停,然后当调用线程二的时候再来通知线程一继续运行。最终运行结果
主要是因为线程一在暂停之后,开始运行线程二随即线程一得到通知可以继续运行,通过代码可以发现都有Thread.Sleep(1000);也就是为了保证两个线程都还在运行期间,线程二移除了集合中的一个元素,那么当线程一再次循环的时候,导致了错误的发生。
早在泛型集合出现之前,非泛型集合一般会提供一个SyncRoot属性,要保证非泛型集合的线程安全,可以通过锁定该属性来实现。如果上面的集合用ArrayList代替,保证线程安全则应该在迭代和删除的时候都加上锁lock,代码如下所示:
static ArrayList list = new ArrayList() { new Person(){ Name="aehyok",Age=25}, new Person(){Name="Kris",Age=23}, new Person(){Name="Leo",Age=26} }; static AutoResetEvent autoSet = new AutoResetEvent(false); static void Main(string[] args) { Thread t1 = new Thread(() => { ///阻止当前线程 autoSet.WaitOne(); lock (list.SyncRoot) { foreach (Person item in list) { Console.WriteLine("t1:" + item.Name); Thread.Sleep(1000); } } }); t1.Start(); Thread t2 = new Thread(() => { ///通知t1可以执行代码 autoSet.Set(); Thread.Sleep(1000); lock (list.SyncRoot) { list.RemoveAt(2); } }); t2.Start(); Console.ReadLine(); }
运行结果就是线程一执行通过
如果你试过,那么会发现泛型集合没有这样的属性来进行加锁,必须要自己创建一个锁定对象来完成同步的任务。
所以第一个例子我们可以这样进行修改
static List<Person> list = new List<Person>() { new Person(){ Name="aehyok",Age=25}, new Person(){Name="Kris",Age=23}, new Person(){Name="Leo",Age=26} }; static object SyncObject = new object(); static AutoResetEvent autoSet = new AutoResetEvent(false); static void Main(string[] args) { Thread t1 = new Thread(() => { ///阻止当前线程 autoSet.WaitOne(); lock (SyncObject) { foreach (var item in list) { Console.WriteLine("t1:" + item.Name); Thread.Sleep(1000); } } }); t1.Start(); Thread t2 = new Thread(() => { ///通知t1可以执行代码 autoSet.Set(); Thread.Sleep(1000); lock (SyncObject) { list.RemoveAt(2); } }); t2.Start(); Console.ReadLine(); }