编写高质量代码改善C#程序的157个建议[动态数组、循环遍历、对象集合初始化]

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

编写⾼质量代码改善C#程序的157个建议[动态数组、循环遍
历、对象集合初始化]
 软件开发过程中,不可避免会⽤到集合,C#中的集合表现为数组和若⼲集合类。

不管是数组还是集合类,它们都有各⾃的优缺点。

如何使⽤好集合是我们在开发过程中必须掌握的技巧。

不要⼩看这些技巧,⼀旦在开发中使⽤了错误的集合或针对集合的⽅法,应⽤程序将会背离你的预想⽽运⾏。

本⽂已更新⾄。

本⽂主要学习记录以下内容:
建议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进⾏遍历我刚刚写了⼀篇有关IEnumerable和IEnumerator两个接⼝的⽂章,有兴趣的话可以看⼀下。

感觉使⽤foreach进⾏循环遍历,总共有三个好处吧:
1、提供了⽐较简单、简洁的语法。

2、⾃动将代码置⼊try-finally块
3、若类型实现IDispose接⼝,foreach会在循环结束后⾃动调⽤Dispose⽅法
18
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 { , AgeScope = p.Age > 10 ? "Old" : "Young" };
foreach (var item in entity)
{
Response.Write(string.Format("name is {0},{1}", , item.AgeScope));
}
AgeScope 属性是经过计算得出的,有了如此⽅便的初始化⽅式,使得代码更加优雅灵活。

相关文档
最新文档