编写高质量代码改善C#程序的157个建议[泛型集合、选择集合、集合的安全]

简介: 前言     软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类。不管是数组还是集合类,它们都有各自的优缺点。如何使用好集合是我们在开发过程中必须掌握的技巧。不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行。

前言

    软件开发过程中,不可避免会用到集合,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();
        }

 

目录
相关文章
|
6月前
|
存储 SQL 数据库连接
C#程序调用Sql Server存储过程异常处理:调用存储过程后不返回、不抛异常的解决方案
本文分析了C#程序操作Sql Server数据库时偶发的不返回、不抛异常问题,并提出了解决思路。首先解析了一个执行存储过程的函数`ExecuteProcedure`,其功能是调用存储过程并返回影响行数。针对代码执行被阻塞但无异常的情况,文章总结了可能原因,如死锁、无限循环或网络问题等。随后提供了多种解决方案:1) 增加日志定位问题;2) 使用异步操作提升响应性;3) 设置超时机制避免阻塞;4) 利用线程池分离主线程;5) 通过信号量同步线程;6) 监控数据库连接状态确保可用性。这些方法可有效应对数据库操作中的潜在问题,保障程序稳定性。
536 11
|
Java 物联网 C#
C#/.NET/.NET Core学习路线集合,学习不迷路!
C#/.NET/.NET Core学习路线集合,学习不迷路!
473 0
|
8月前
|
Java 测试技术 C#
浅谈 C# 13 中的 params 集合
浅谈 C# 13 中的 params 集合
197 5
|
11月前
|
开发框架 .NET Java
C#集合数据去重的5种方式及其性能对比测试分析
C#集合数据去重的5种方式及其性能对比测试分析
161 11
|
11月前
|
开发框架 .NET Java
C#集合数据去重的5种方式及其性能对比测试分析
C#集合数据去重的5种方式及其性能对比测试分析
190 10
|
11月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
547 13
|
开发框架 NoSQL MongoDB
C#/.NET/.NET Core开发实战教程集合
C#/.NET/.NET Core开发实战教程集合
243 1
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
1110 0
|
2月前
|
XML 前端开发 C#
C#编程实践:解析HTML文档并执行元素匹配
通过上述步骤,可以在C#中有效地解析HTML文档并执行元素匹配。HtmlAgilityPack提供了一个强大而灵活的工具集,可以处理各种HTML解析任务。
184 19
|
3月前
|
监控 算法 C#
C#与Halcon联合编程实现鼠标控制图像缩放、拖动及ROI绘制
C#与Halcon联合编程实现鼠标控制图像缩放、拖动及ROI绘制
548 0

热门文章

最新文章

下一篇
oss云网关配置