现在的位置: 首页 > 综合 > 正文

【笔记】《编写高质量代码:改善c#程序的157个建议》-第1章 基本语言要素(SamWang)

2013年03月22日 ⁄ 综合 ⁄ 共 10954字 ⁄ 字号 评论关闭
30元程序员衣装优惠券,仅剩3天!点击领取

**************************************************************************

该书在线阅读:编写高质量代码:改善C#程序的157个建议

源代码下载:点我下载

**************************************************************************

 

第1章 基本语言要素 / 2

 

-------------------------------

建议1:正确操作字符串 / 2

-------------------------------

  • 确保尽量少的装箱
  • 避免分配额外的内存空间

  注:string是个特殊的引用类型,一旦赋值就不可改变。在运行时调用System.String类中的任何方法或进行任何运算,都会在内存中创建一个新的字符串对象,这也意味着要为该新对象分配新的内存空间。

    而StringBuilder不会重新创建一个string对象。

    所以一旦需要对string类型进行多次操作,就应该用StringBulider,减少性能损耗!

 

 

---------------------------------

建议2:使用默认转型方法 /6 

---------------------------------

  • 使用类型的转换运算符:

   implicit (隐式)、explicit(显示) + operator,同时必须加上public与static

      public static implicit operator Cat(Animal a)
      {
         return new Cat() { Name = "Cat:" + a.Name };
      }

    (一般不建议用户对自己的类型重载转换运算符。如需用户自定义的类型之间需要转换,建议从面向对象的角度考虑,因为它们一般都含有某种关系(如继承、实现等)。在这种情况下,就应该使用第四种方法:CLR支持的转型。) 

  • 使用类型内置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法。
  • 使用帮助类提供的方法。

   即System.Convert。该类还支持将任何自定义类型转换为任何基元类型,只需继承IConvertible接口即可。

    System.BitConvert提供了基元类型与字节数组之间相互转换的方法。

    注意 所谓“基元类型”:是指编译器直接支持的数据类型,即直接映射到FCL中的类型。基元类型包括:sbyte / byte / short / ushort / int / uint / long / ulong / char / float / double / bool / decimal /object / string。

  • 使用CLR支持的转型

    即上溯转型和下溯转型。也是我们平时说的向上转换和向下转换。实际上就是基类与子类之间的相互转换。

         Animal animal;          //Animal为基类
          Dog dog = new Dog();    //Dog为Animal的子类
          animal = dog;           //隐式转换,通过。
          //dog = animal;         //编译不通过。基类到子类不支持隐式
          dog = (Dog)animal;      //通过,执行成功!

          //此处需注意,上面能显示转换animal = dog这句话,此次animal保存的对象来之dog
         Animal a = new Animal();
         Dog d = (Dog)a;         //编译通过,执行失败,不允许将Animal类型转换为Dog类型

 

 

------------------------------------------

建议3:区别对待强制转型与as和is /9 

------------------------------------------

  如果类型之间都上溯到某个共同的基类,那么根据此基类进行的转型(即基类转型为子类本身)应该使用as。子类与子类之间的转型,则应该提供转换操作符,以便进行强制转型。

  当能使用as的情况下,都应该使用as,因为as更安全效率更高。而且结合is使用更加完美。

  但as有个问题,即它不能操作基元类型。

 

 

---------------------------------------

建议4:TryParse比Parse好 / 12 

---------------------------------------

  TryParse无需处理异常,效率快于Parse,尤其是在解析失败的时候!因此也有了一种模式叫TryParse模式。

 

 

-----------------------------------------------------

建议5:使用int?来确保值类型也可以为null / 15  

-----------------------------------------------------

  从.net2.0开始,FCL中提供了一个额外的类型:可以为空的类型Nullable<T>,简写T?

  它是个结构体,只有值类型才可以作为“可空类型”(引用类型本身就能为NULL)。

  基元类型能隐式转换为可空类型:  

  int i = 0;
  int? j = i;

  但可空类型不能直接转换为基元类型,需要使用null 合并运算符:??

  ?? 运算符定义当可以为 null 的类型分配给非可以为 null 的类型时返回的默认值。

  int? i = null;
  int j = i??0; //j=0

 博文链接:c#各种运算符

 

-----------------------------------------------------

建议6:区别readonly和const的使用方法 / 16

-----------------------------------------------------

  • const是一个编译器变量,static readonly是一个运行时常量
  • const只能修饰基元类型、枚举类型或字符串类型,readonly没有限制。
  • readonly只能用于类成员,不能用于方法的局部变量。const无此限制。
  • const 字段只能在该字段的声明中初始化。 readonly 字段可以在声明或构造函数中初始化。(对于实例字段,在包含字段声明的类的实例构造函数中;或者,对于静态字段,在包含字段声明的类的静态构造函数中)

   注意:

  1. const本身是编译期常量,所以就是static的,加上static会编译错误。
  2. readonly灵活性大于const,但性能却略低于const(极小)。所以推荐尽量使用readonly。
  3. readonly变量是运行时变量,只能在声明或构造函数中初始化(能在构造函数中多次赋值),在其他地方“不可以更改”。

    “不可以更改”有两层含义:

    • 对于值类型变量,值本身不可以改变(readonly,只读)
    • 对于引用类型变量,引用本身(相当于指针)不可改变,但是其成员可被改变。
      Sample2 sample2 = new Sample2(new Student() { Age = 10 });
      sample2.ReadOnlyValue.Age = 20; //成功 

  博文链接:C# const和static readonly区别

 

 

-----------------------------------------

建议7:将0值作为枚举的默认值 / 19

-----------------------------------------

   看下面的例子:

 1         enum Week
 2         {
 3             Monday = 1,
 4             Tuesday = 2,
 5             Wednesday = 3,
 6             Thursday = 4,
 7             Friday = 5,
 8             Saturday = 6,
 9             Sunday = 7
10         }
11 
12         static Week week;
13 
14         static void Main(string[] args)
15         {
16             Console.WriteLine(week); //0
17         }

  输出结果为:0;因为枚举内容为int类型。所以默认值始终取0。

  同时还能给枚举赋值其他整型数值。  

Week week = (Week)9;

 

 

------------------------------------------------------

建议8:避免给枚举类型的元素提供显式的值 / 20

------------------------------------------------------

  先看段例子:

 1         enum Week
 2         {
 3             Monday = 1,
 4             Tuesday = 2,
 5             ValueTemp,
 6             Wednesday = 3,
 7             Thursday = 4,
 8             Friday = 5,
 9             Saturday = 6,
10             Sunday = 7
11         }
12 
13         static void Main(string[] args)
14         {
15             Week week = Week.ValueTemp;
16             Console.WriteLine(week);
17             Console.WriteLine(week == Week.Wednesday);
18 
19         }

输出结果为:

  Wednesday

  True

   

  红色的ValueTemp就是新增加的枚举值,出现上面的问题是因为当枚举元素没有被显示赋值时,编译器会为那些未赋值元素逐个+1赋值。

  因此ValueTemp被赋值为3。而枚举中允许出现重复值,也就是多次赋值效果。换句话说3被赋值给Wednesday。

 

 

---------------------------------

建议9:习惯重载运算符 / 22

---------------------------------  

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Salary mikeIncome = new Salary() { RMB = 22 };
 6             Salary roseIncome = new Salary() { RMB = 33 };
 7             //Salary familyIncome = Salary.Add(mikeIncome, roseIncome);
 8             Salary familyIncome = mikeIncome + roseIncome;
 9         }
10     }
11 
12     class Salary
13     {
14         public int RMB { get; set; }
15 
16         public static Salary operator +(Salary s1, Salary s2)
17         {
18             s2.RMB += s1.RMB;
19             return s2;
20         }
21     }

 

 

----------------------------------------------------------------

建议10:创建对象时需要考虑是否实现比较器 / 23

----------------------------------------------------------------

  一般对需要比较或排序的对象,继承IComparable<T>接口,实现默认比较器。如果需要其他比较可以如下例子中创建非默认的比较器。

View Code

 1      class Program
 2      {
 3          static void Main(string[] args)
 4          {
 5              List<Salary> companySalary = new List<Salary>()
 6                  {
 7                      new Salary() { Name = "Mike", BaseSalary = 3000, Bonus = 1000 },
 8                      new Salary() { Name = "Rose", BaseSalary = 2000, Bonus = 4000 },
 9                      new Salary() { Name = "Jeffry", BaseSalary = 1000, Bonus = 6000 },
10                      new Salary() { Name = "Steve", BaseSalary = 4000, Bonus = 3000 }
11                  };
12              companySalary.Sort(); //根据自带的进行排序,按BaseSalary
13              //companySalary.Sort(new BonusComparer());    //提供一个非默认的比较器,按Bonus
14              foreach (Salary item in companySalary)
15              {
16                  Console.WriteLine(string.Format("Name:{0} \tBaseSalary:{1} \tBonus:{2}", item.Name, item.BaseSalary, item.Bonus));
17              }
18          }
19      }
20  
21      class Salary : IComparable<Salary>
22      {
23          public string Name { get; set; }
24          public int BaseSalary { get; set; }
25          public int Bonus { get; set; }
26  
27          #region IComparable<Salary> 成员
28  
29          public int CompareTo(Salary other)
30          {
31              return BaseSalary.CompareTo(other.BaseSalary);
32          }
33  
34          #endregion
35      }
36  
37      class BonusComparer : IComparer<Salary>
38      {
39          #region IComparer<Salary> 成员
40  
41          public int Compare(Salary x, Salary y)
42          {
43              return x.Bonus.CompareTo(y.Bonus);
44          }
45  
46          #endregion
47      }

 

 

 

-----------------------------------------

建议11:区别对待==和Equals / 27

-----------------------------------------

  相等性比较主要有三种:运算符==、Equals、ReferenceEquals(引用比较)。

  • 对于值类型,如果类型的值相等,就应该返回True。
  • 对于引用类型,如果类型指向同一个对象,则返回True。
View Code

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //ValueTypeOPEquals();
 6             //ReferenceTypeOPEquals();
 7             //ValueTypeEquals();
 8             ReferenceTypeEquals();
 9         }
10 
11         static void ValueTypeOPEquals()
12         {
13             int i = 1;
14             int j = 1;
15             //True
16             Console.WriteLine(i == j);
17             j = i;
18             //True
19             Console.WriteLine(i == j);
20         }
21 
22         static void ReferenceTypeOPEquals()
23         {
24             object a = 1;
25             object b = 1;
26             //False
27             Console.WriteLine(a == b);
28             b = a;
29             //True
30             Console.WriteLine(a == b);
31         }
32 
33         static void ValueTypeEquals()
34         {
35             int i = 1;
36             int j = 1;
37             //True
38             Console.WriteLine(i.Equals(j));
39             j = i;
40             //True
41             Console.WriteLine(i.Equals(j));
42         }
43 
44 
45         static void ReferenceTypeEquals()
46         {
47             object a = new Person("NB123");
48             object b = new Person("NB123");
49             //False,重载后True
50             Console.WriteLine(a.Equals(b));
51             Console.WriteLine(a.Equals(b as Person));
52             Console.WriteLine(a as Person == b as Person);
53             //false
54             Console.WriteLine(a == b); //用object的==判断
55             Console.WriteLine(object.ReferenceEquals(a,b));
56             b = a;
57             //True
58             Console.WriteLine(a.Equals(b));
59             Console.WriteLine(a.Equals(b as Person));
60             Console.WriteLine(a as Person == b as Person);
61             Console.WriteLine(a == b);
62             Console.WriteLine(object.ReferenceEquals(a, b));
63         }
64     }
65 
66     class Person
67     {
68         public string IDCode { get; private set; }
69 
70         public Person(string idCode)
71         {
72             this.IDCode = idCode;
73         }
74 
75         public override bool Equals(object obj)
76         {
77             return IDCode == (obj as Person).IDCode;
78         }
79 
80         public bool Equals(Person p)
81         {
82             return IDCode == p.IDCode;
83         }
84 
85         public static bool operator ==(Person p1, Person p2)
86         {
87             return p1.IDCode == p2.IDCode;
88         }
89 
90         /// <summary>
91         /// 必须同时重载==与!=
92         /// </summary>
93         public static bool operator !=(Person p1, Person p2)
94         {
95             return !(p1 == p2);
96         }
97     }

 

 

-----------------------------------------------------------

建议12:重写Equals时也要重写GetHashCode / 29

-----------------------------------------------------------

   例子:

View Code

 1     class Program
 2     {
 3         static Dictionary<Person, PersonMoreInfo> PersonValues = new Dictionary<Person, PersonMoreInfo>();
 4         static void Main(string[] args)
 5         {
 6             AddAPerson();
 7             Person mike = new Person("NB123");
 8             Console.WriteLine(mike.GetHashCode());
 9             Console.WriteLine(PersonValues.ContainsKey(mike));
10 
11             //string str1 = "NB0903100006";
12             //string str2 = "NB0904140001";
13             //Console.WriteLine(str1.GetHashCode());
14             //Console.WriteLine(str2.GetHashCode());
15         }
16 
17         static void AddAPerson()
18         {
19             Person mike = new Person("NB123");
20             PersonMoreInfo mikeValue = new PersonMoreInfo() { SomeInfo = "Mike's info" };
21             PersonValues.Add(mike, mikeValue);
22             Console.WriteLine(mike.GetHashCode());
23             Console.WriteLine(PersonValues.ContainsKey(mike));
24         }
25 
26     }
27 
28     class Person : IEquatable<Person>
29     {
30         public string IDCode { get; private set; }
31 
32         public Person(string idCode)
33         {
34             this.IDCode = idCode;
35         }
36 
37         public override bool Equals(object obj)
38         {
39             return IDCode == (obj as Person).IDCode;
40         }
41 
42         public override int GetHashCode()
43         {
44             //return this.IDCode.GetHashCode();
45             return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + this.IDCode).GetHashCode();
46         }
47 
48         public bool Equals(Person other)
49         {
50             return IDCode == other.IDCode;
51         }
52     }
53 
54     class PersonMoreInfo
55     {
56         public string SomeInfo { get; set; }
57     }

  上面代码中当不重写GetHashCode时输出为:

  

   基于键值的集合(如上面的DIctionary)会根据Key值来查找Value值。CLR内部会优化这种查找,实际上,最终是根据Key值的HashCode来查找Value值。

  Object为所有的CLR类型都提供了GetHashCode的默认实现。每new一个对象,CLR都会为该对象生成一个固定的整型值,该整型值在对象的生存周期内不会改变,而该对象默认的GetHashCode实现就是对该整型值求HashCode。

  简单重写GetHashCode,

        public override int GetHashCode()
        {
            return this.IDCode.GetHashCode();
        }

  输出为:

  

  尽管这里GetHashCode已经实现了,但是还存在另外一个问题,它永远只返回一个整型类型,而整型类型的容量显然无法满足字符串的容量,以下的例子就能产生两个同样的HashCode。

      string str1 = "NB0903100006";
     string str2 = "NB0904140001";
     Console.WriteLine(str1.GetHashCode());
     Console.WriteLine(str2.GetHashCode());

   

  为了减少两个不同类型之间根据字符串产生相同的HashCode的几率,一个稍作改进版本的GetHashCode方法:  

        public override int GetHashCode()
        {
            return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + this.IDCode).GetHashCode();
        }

    

 

-------------------------------------------- 

建议13:为类型输出格式化字符串 / 32 

--------------------------------------------

  输出格式化字符串一般有两种

  • 简单重写ToString()方法
  • 继承IFormattable接口,实现其方法ToString。

  代码如下:

View Code

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Person person = new Person() { FirstName = "Jessica", LastName = "Hu", IDCode = "NB123" };
 6             Console.WriteLine(person.ToString());
 7             Console.WriteLine(person.ToString("Ch", null));
 8             Console.WriteLine(person.ToString("En", null));
 9         }
10     }
11 
12     class Person : IFormattable
13     {
14         public string IDCode { get; set; }
15         public string FirstName { get; set; }
16         public string LastName { get; set; }
17 
18         //实现接口IFormattable的方法ToString
19         public string ToString(string format, IFormatProvider formatProvider)
20         {
21             switch (format)
22             {
23                 case "Ch":
24                     return this.ToString();
25                 case "En":
26                     return string.Format("{0} {1}", FirstName, LastName);
27                 default:
28                     return this.ToString();
29             }
30         }
31 
32         //重写Object.ToString()
33         public override string ToString()
34         {
35             return string.Format("{0} {1}", LastName, FirstName);
36         }
37     }

  上面这种方法是在意识到类型会存在格式化字符串输出方面的需求时没提起为类型继承了接口IFormattable。如果类型本身没有提供格式化输出的功能,这个时候,格式化器就派上了用场。格式化器的好处就是可以根据需求的变化,随时增加或者修改它。

View Code

 1     class Program
2 {
3 static void Main(string[] args)
4 {
5 Person person = new Person() { FirstName = "Jessica", LastName = "Hu", IDCode = "NB123" };
6 Console.WriteLine(person.ToString());
7 PersonFomatter pFormatter = new PersonFomatter();
8 Console.WriteLine(pFormatter.Format("Ch", person, null));
9 Console.WriteLine(pFormatter.Format("En", person, null));
10 Console.WriteLine(pFormatter.Format("ChM", person, null));
11 }
12 }
13
14 class Person
15 {
16 public string IDCode { get; set; }
30元程>序员衣装优惠券,仅剩3天!点击领取

抱歉!评论已关闭.