C#程序编写高质量代码改善的157个建议【16-19】[动态数组、循环遍历、对象集合初始化]
前言
软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类。不管是数组还是集合类,它们都有各自的优缺点。如何使用好集合是我们在开发过程中必须掌握的技巧。不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行。
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:
建议16、元素数量可变的情况下不应使用数组
建议17、在多数情况下使用foreach进行循环遍历
建议18、foreach不能代替for
建议19、使用更有效的对象和集合初始化
建议16、元素数量可变的情况下不应使用数组
在C#中,数组一旦被创建,长度就不能改变。如果我们需要一个动态且可变长度的集合,就应该使用ArrayList或List<T>来创建。而数组本身,尤其是一维数组,在遇到要求高效率的算法时,则会专门被优化以提升其效率。一维数组也成为向量,其性能是最佳的,在IL中使用了专门的指令来处理它们。
从内存使用的角度来讲,数组具有以下特点:
1、数组在创建时被分配了一段固定长度的内存。
2、如果数组元素是值类型,则每个元素的长度等于相应的值类型的长度
3、如果数组的元素是引用类型,则每个元素的长度为该引用类型的IntPtr.Size。
4、数组的存储结构一旦被分配,就不能再变化。
而ArryaList是这样的:
1、ArrayList是链表结构,可以动态增减内存空间。
2、如果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要高很多很多。
建议17、在多数情况下使用foreach进行循环遍历
这里关于如何针对集合才能使用foreach进行遍历我刚刚写了一篇有关IEnumerable和IEnumerator两个接口的文章,有兴趣的话可以看一下。http://www.cnblogs.com/aehyok/p/3641193.html
感觉使用foreach进行循环遍历,总共有三个好处吧:
1、提供了比较简单、简洁的语法。
2、自动将代码置入try-finally块
3、若类型实现IDispose接口,foreach会在循环结束后自动调用Dispose方法
建议18、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循环。
建议19、使用更有效的对象和集合初始化
对象初始化设定项支持可以直接在大括号中对自动实现的属性进行赋值。
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 属性是经过计算得出的,有了如此方便的初始化方式,使得代码更加优雅灵活。
C#程序编写高质量代码改善的157个建议【16-19】[动态数组、循环遍历、对象集合初始化]的更多相关文章
- C#程序编写高质量代码改善的157个建议【20-22】[泛型集合、选择集合、集合的安全]
建议20.使用泛型集合来替代非泛型集合 http://www.cnblogs.com/aehyok/p/3384637.html 这里有一篇文章,是我之前专门来介绍泛型的.我们应尽量的使用泛型集合.因 ...
- C#程序编写高质量代码改善的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]
前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正. 建议1. ...
- C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...
- C#程序编写高质量代码改善的157个建议【10-12】[创建对象时需要考虑是否实现比较器、区别对待==和Equals]
前言 建议10.创建对象时需要考虑是否实现比较器 建议11.区别对待==和Equals 建议12.重写Equals时也要重写GetHashCode 建议10.创建对象时需要考虑是否实现比较器 有对象的 ...
- C#程序编写高质量代码改善的157个建议【4-9】[TryParse比Parse、使用int?来确保值类型也可以为null、readonly和const、0值设为枚举的默认值、避免给枚举类型的元素提供显式的值、习惯重载运算符]
建议4.TryParse比Parse好 如果注意观察,除string之外的所有的基元类型.会发现它们都有两个将字符串转换为自身类型的方法:Parse和TryParse.以类型double为例. 两者最 ...
- 博友的 编写高质量代码 改善java程序的151个建议
编写高质量代码 改善java程序的151个建议 http://www.cnblogs.com/selene/category/876189.html
- 编写高质量代码--改善python程序的建议(六)
原文发表在我的博客主页,转载请注明出处! 建议二十八:区别对待可变对象和不可变对象 python中一切皆对象,每一个对象都有一个唯一的标识符(id()).类型(type())以及值,对象根据其值能否修 ...
- 编写高质量代码--改善python程序的建议(八)
原文发表在我的博客主页,转载请注明出处! 建议四十一:一般情况下使用ElementTree解析XML python中解析XML文件最广为人知的两个模块是xml.dom.minidom和xml.sax, ...
- 编写高质量代码改善java程序的151个建议——导航开篇
2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...
随机推荐
- 创建指定python版本的虚拟环境
使用virtualenvwrapper管理虚拟环境 鉴于virtualenv不便于对虚拟环境集中管理,所以推荐直接使用virtualenvwrapper. virtualenvwrapper提供了一系 ...
- Python和Java的区别
这里是我的一些总结,有些是参考别人的(在这里谢谢!!!) 区别: 1.Python比Java简单,学习成本低,开发效率高2.Java运行效率高于Python,尤其是纯Python开发的程序,效率极低3 ...
- ganglia 服务端
#!/bin/bash #配置参数 serverIP=192.168.1.16 network=ens32 #关闭selinux setenforce sed -i 's/SELINUX=enforc ...
- Lua和C交互的简易教程
转载请标明出处:http://blog.csdn.net/shensky711/article/details/52458051 本文出自: [HansChen的博客] Lua栈 要理解Lua和C++ ...
- 设计模式之观察者模式--PHP
列举一个场景:下班之后回家,打开家门,开始做饭,之后睡觉 以上场景如果按照传统的开始方式就是封装一个用户类,里面有回家方法,打开门方法,做饭方法,睡觉方法,之后在外面依次调用. 假设你代码开发完了,这 ...
- IDEA如何重置窗口布局
如何重置窗口布局 我不知道怎么搞的,左边的,上边的,下边的,视图都没有了 , 重启了一下,然后重置为默认视图,就好了
- C#DateTime格式转换全介绍
DateTime与字符串转换: DateTime()与转换为字符串主要依靠DateTime().ToString(string format) 函数,以我的理解,参数format大体分为单个字母和多个 ...
- TypeError: 'list' object cannot be interpreted as an integer
TypeError: 'list' object cannot be interpreted as an integer 类型错误,不能将list对象转换为一个整数. 错误代码,例如如下例子: arg ...
- P2910 [USACO08OPEN]寻宝之路Clear And Present Danger |Floyd
题目描述 农夫约翰正驾驶一条小艇在牛勒比海上航行. 海上有N(1≤N≤100)个岛屿,用1到N编号.约翰从1号小岛出发,最后到达N号小岛. 一张藏宝图上说,如果他的路程上经过的小岛依次出现了Ai,A2 ...
- SI4432和SI1212通讯
SI4432是美国Silabs研发的一款工作频段低于1GHz的高性能射频收发芯片,属于其EzRadioPro系类.在国内已经广泛应用于工业.科研.医疗(ISM)以及短距离无线通信设备(SRD),其输出 ...