前言html
软件开发过程当中,不可避免会用到集合,C#中的集合表现为数组和若干集合类。不论是数组仍是集合类,它们都有各自的优缺点。如何使用好集合是咱们在开发过程当中必须掌握的技巧。不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行。算法
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录如下内容:数组
建议1六、元素数量可变的状况下不该使用数组函数
建议1七、在多数状况下使用foreach进行循环遍历性能
建议1八、foreach不能代替for学习
建议1九、使用更有效的对象和集合初始化优化
建议1六、元素数量可变的状况下不该使用数组this
在C#中,数组一旦被建立,长度就不能改变。若是咱们须要一个动态且可变长度的集合,就应该使用ArrayList或List<T>来建立。而数组自己,尤为是一维数组,在遇到要求高效率的算法时,则会专门被优化以提高其效率。一维数组也成为向量,其性能是最佳的,在IL中使用了专门的指令来处理它们。spa
从内存使用的角度来说,数组具备如下特色:pwa
一、数组在建立时被分配了一段固定长度的内存。
二、若是数组元素是值类型,则每一个元素的长度等于相应的值类型的长度
三、若是数组的元素是引用类型,则每一个元素的长度为该引用类型的IntPtr.Size。
四、数组的存储结构一旦被分配,就不能再变化。
而ArryaList是这样的:
一、ArrayList是链表结构,能够动态增减内存空间。
二、若是ArrayList存储的是值类型,则会为每一个元素增长12字节的空间,其中4字节用于对象引用,8字节是元素装箱时引入的对象头。
而List<T>是ArrayList的泛型实现,它省去了拆箱和装箱带来的开销。
若是必定要动态改变数组的长度,一种方法是将数组转换为ArrayList或List<T>,以下面的代码所示:
///定义一个一维数组 int[] iArr = { 0,1,3,4,6,7,9}; ///将数组转换为ArrayList ArrayList arrayListInt = ArrayList.Adapter(iArr); arrayListInt.Add(11); ///将数组转换为List<T> List<int> listInt = iArr.ToList<int>(); listInt.Add(11);
还有一种方法是用数组的复制功能。数组继承自System.Array,抽象类System.Array提供了一些有用的实现方法,其中就包含了Copy方法,它负责将一个数组的内容复制到另一个数组中。不管是哪一种方法,改变数组长度就至关于从新建立了一个数组对象。
为了让数组看上去自己就具备动态改变长度的功能,还能够建立一个名为ReSize的扩展方法。
public static class ClassForExtensions { public static Array ReSize(this Array array,int newSize) { Type t = array.GetType().GetElementType(); Array newArray = Array.CreateInstance(t, newSize); Array.Copy(array, 0, newArray, 0, Math.Min(array.Length, newSize)); return newArray; } }
调用方式以下:
static void Main(string[] args) { int[] iArr = { 0,1,3,4,6,7,9}; iArr = (int[])ClassForExtensions.ReSize(iArr, 20); Console.ReadLine(); }
下面咱们来对比一下性能,先来看代码:
class Program { static void Main(string[] args) { ResizeArray(); ResizeList(); Console.ReadLine(); } public static void ResizeArray() { int[] iArr = {0,1,3,4,6,8 }; Stopwatch watch = new Stopwatch(); watch.Start();///用于测量时间间隔 iArr = (int[])iArr.ReSize(10); watch.Stop();/// Console.WriteLine("ResizeArray:{0}", watch.Elapsed); } public static void ResizeList() { List<int> iArr = new List<int>(new int[] { 0, 1, 3, 4, 6, 8, 9 }); Stopwatch watch = new Stopwatch(); watch.Start(); iArr.Add(0); iArr.Add(0); iArr.Add(0); watch.Stop(); Console.WriteLine("ResizeList:{0}", watch.Elapsed); } }
Main函数中主要是调用,本身定义的两个方法,第一个是从新设置数组的长度,第二个是设置List<T>的长度,经过运行时间进行测量:
严格意义上讲,List<T>不存在改变长度的说法,此处主要是来进行对比一下,对List<T>设置长度,而且进行赋值,即使是这样,在时间效率上ResizeList比ResizeArray要高不少不少。
建议1七、在多数状况下使用foreach进行循环遍历
这里关于如何针对集合才能使用foreach进行遍历我刚刚写了一篇有关IEnumerable和IEnumerator两个接口的文章,有兴趣的话能够看一下。http://www.cnblogs.com/aehyok/p/3641193.html
感受使用foreach进行循环遍历,总共有三个好处吧:
一、提供了比较简单、简洁的语法。
二、自动将代码置入try-finally块
三、若类型实现IDispose接口,foreach会在循环结束后自动调用Dispose方法
建议1八、foreach不能代替for
foreach存在一个问题是:它不支持循环时对集合进行增删操做。咱们来看一下简单的例子:
List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; foreach (int item in list) { list.Remove(item); Console.WriteLine(item.ToString()); } Console.ReadLine();
一块儿看一下执行结果:
那么下面咱们来使用for进行尝试:
List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; for (int i = 0; i < list.Count(); i++) { list.Remove(list[i]); } Console.ReadLine();
进行删除确定是没问题的。可是要仔细看一下,好比它第一次删除索引0的时候,也就是删除了1,那么它会当即从新调整索引,而后第二次删除的时候,删除的不是2,而是3这个项。那么最终运行完发现还剩余两项
foreach循环使用了迭代器进行集合的遍历,它在FCL提供的迭代器内部维护了一个对集合版本的控制。那么什么是集合版本呢?简单的说,其实它就是一个整型的变量,任何对集合的增删操做都会使版本号加1。foreach循环会调用MoveNext方法来遍历元素,在MoveNext方法内部会进行版本号的检测,一旦检测到版本号有变更,就会抛出InvalidOperationException异常。
若是使用for循环就不会带来这样的问题。for直接使用所引器,它不对集合版本号进行判断,因此不存在由于集合的变更而带来的异常(固然,超出索引长度这种状况除外)。
索引,由于版本检测的缘故,foreach循环并不能带起for循环。
建议1九、使用更有效的对象和集合初始化
对象初始化设定项支持能够直接在大括号中对自动实现的属性进行赋值。
class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { Person person = new Person() { Name = "aehyok", Age = 25 }; Console.ReadLine(); } }
以往只能依靠构造方法传值进去,或者在对象构造完毕后对属性进行赋值。如今这些步骤简化了,初始化设定项实际至关于编译器在对象生成后对属性进行了赋值。
class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { Person person = new Person() { Name = "Kris", Age = 22 }; List<Person> personList = new List<Person>() { new Person() { Name = "aehyok", Age = 25 }, person, null }; Console.ReadLine(); } }
使用集合的初始化设定项,编译器会在集合对象建立完毕后对集合调用Add方法。上面这段代码展现了如何在初始化语句中建立一个新对象或一个现有对象,以及一个null值。
不过,初始化设定项毫不仅仅是为了对象和集合初始化的方便,它更重要的做用是为LINQ查询中的匿名类型进行属性的初始化。因为LINQ查询返回的集合中匿名类型的属性都是只读的,若是须要为匿名类型属性赋值,或者增长属性,只能经过初始化设定项来进行。初始化设定项还能为属性使用表达式。
来看一段代码:
List<Person> lst = new List<Person>() { new Person(){ Age = 10,Name="Tommy"}, new Person(){ Age = 20,Name="Sammy"} }; var entity = from p in lst select new { p.Name, AgeScope = p.Age > 10 ? "Old" : "Young" }; foreach (var item in entity) { Response.Write(string.Format("name is {0},{1}", item.Name, item.AgeScope)); }
AgeScope 属性是通过计算得出的,有了如此方便的初始化方式,使得代码更加优雅灵活。