1. 程式人生 > >C# 《編寫高質量代碼改善建議》整理&筆記 --(一)基本語言篇

C# 《編寫高質量代碼改善建議》整理&筆記 --(一)基本語言篇

生成 hash bin AS 指向 對象 ... 常量 compareto

題記:這是自己的觀後感,工作兩年了,本來打算好好學習設計模式,或者作為客戶端深入了解GPU編程的,但是突然發現還有這麽一本書。

《編寫高質量代碼改善建議》,感覺這正是自己需要的。

我是做遊戲開發的,最近一段時間工作,接觸到了數學編程,涉及到大量的計算,策劃那邊增改需求也很多,加上我的項目對性能要求很高。微量的計算影響到

性能。所以對代碼質量要求很高,明顯自己的代碼質量已經不達標了。所以,我還是打牢固基礎,編寫高質量代碼才是王道。

之前工作經歷了很多其他人的代碼,什麽工作三四年,甚至四五年的代碼都是慘不忍睹,我都是“瞧不起”的。

自己不想成為“自己眼中的他們”。這本書,大部分自己都是會的,但是總結的相當到位。畢竟

也有兩年經驗了,打算用3個月時間,看完,高質量的寫完讀後感,當然其中也會加入自己的通俗易懂的看法,取其精華。

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

1.正確操作字符串。

1)盡量避免少的裝箱。

string str1 = "djw" + 9;
string str2 = "djw" + 9.ToString(); (高效)

第一行代碼會發生一次裝箱操作。

第二行代碼沒有,因為調用的是整型的ToString方法。

(整型ToString()是直接通過操作內存來完成int到string的轉換,效率比裝箱高很多。)

補充說明:

大家都知道無論值類型還是引用類型其本質上都是object類型,即引用類型。

裝箱:將值類型轉為引用類型(object)。(通俗來說,將一個單一的東西轉為萬能的,當然慢)

拆箱:引用類型轉為值類型。(將萬能轉為單一東西,當然快)

2)避免分配額外的內存空間。

string是個很特殊的對象,一旦賦值不可改變。運行時調用System.String類中任何對象進行運算, = , + 等,都會在內存中

創建新的字符串對象,這意味著為該對象分配新的內存空間。

運行時額外開銷:

 private static void Method1()
        {
            string s1 = "aaa";
            s1 = "111" + s1 + "222";
            //創建了3個字符串對象,(開辟三個內存空間),並調用一次string.Contract
        }
        
        private static void Method2()
        {
            string s1 = 9 + "222";
            //發生一次裝箱操作,調用一次string.Contract方法
        }
          
        private static void Method3()
        {
            string s1 = "ss" + "dd" + "cc";
            //相當於 s1 = "ssddcc";
        }

        private static void Method4()
        {
            const string a = "t"; //因為a 是一個常量
            string s1 = "abs" + a;
            //該代碼等效於 s1 = "abs" +"t"
            //等效於           s1 = "abst";
        }

對於操作頻繁的明顯帶來性能消耗,可以用StringBuilder來彌補。可以參考我這篇帖子:http://www.cnblogs.com/u3ddjw/p/6823346.html

string.format內部方法使用StringBuilder進行字符串公式化。

2.區別const 和 readonly的使用方法

const,應用上更加效率。

編譯期常量,因為如此,所以天然就是static,不能再前面增加static關鍵字修飾。

之所以const效率高,是因為經過編譯器編譯之後,我們在代碼中引用const的地方會用const變量所對應的實際值來代替。

只能修飾基元類型,枚舉類型或字符串類型。

readonly,大部分應用情況,“效率”並沒有那麽高的地位,我們更願意采用readonly,因為readonly賦予代碼更多的靈活性。

運行時常量。第一次被賦值後將不可改變。(這句話,不完全正確,再加上"除了在構造函數中,可以被賦值多次”)

修飾沒有限制。

readonly 屬於類實例的成員,要使他成為類的成員,需要在前面加上static,這樣就可以直接使用類名調用.

3.枚舉的正確使用

  1)將0 作為枚舉的默認值。

  2) 避免給枚舉類型的元素提供顯示的值。

    因為如果沒有給元素顯示的賦值,編譯器會逐個為元素的值 + 1.

  

4.創建對象時考慮是否實現比較器

  編碼中,一般我們實現.Net自帶的類,方法比較多,但是很多時候,接口,也給我們提供很多方便的功能。我們用的比較少(可能我學的還是比較淺吧)。

比較器的使用,真的很方便,我直接上可使用的代碼。

  

 public class Salary:IComparable<Salary>
    {
        public int BaseSalary { get; set; }
        public int Bonus { get; set; }

        public int CompareTo(Salary sa)
        {
            return BaseSalary.CompareTo(sa.BaseSalary);
        }

        public Salary(int baseSalary,int bonus)
        {
            this.BaseSalary = baseSalary;
            this.Bonus = bonus;
        }
    }

  //自定義排序(可根據該,進行擴展,實現你需要的類型比較,是不是超方便)
    public class SortSalaryBonus:IComparer<Salary>
    {
        public int Compare(Salary sa,Salary sb)
        {
            return sa.Bonus.CompareTo(sb.Bonus);
        }
    }


   static void Main(string[] args)
        {
            List<Salary> salarys = new List<Salary>();
            salarys.Add(new Salary(2, 10));
            salarys.Add(new Salary(21, 1210));
            salarys.Add(new Salary(22, 10));
            salarys.Add(new Salary(233, 120));
            salarys.Sort();
            for (int i = 0; i < salarys.Count; i++)
            {
                Console.WriteLine(salarys[i].BaseSalary);
            }
            salarys.Sort(new SortSalaryBonus());
            for (int i = 0; i < salarys.Count;i++ )
            {
                Console.WriteLine(salarys[i].Bonus);
            }
                Console.Read();
        }

5.區別對待 == 和 Equals

首先,要明確“相等性”的概念。CLR中將“相等性”分為兩類,“值相等”和“引用相等”。

如果用來比較的兩個變量所包含的數值相等,那麽將其定義為“值相等”;

如果比較的兩個變量引用的是同一內存,那麽將其定義為“引用相等”。

但是無論 == 還是 equals,都一個原則:

對於值類型,如果類型的值相等,就返回true。

對於引用類型,如果類型指向同一個對象,則返回true。

但是這裏Equals有什麽跟==的區別?

我們寫一個簡單的Person類。

   

  public class Person
    {
        public string IdCode;
        public Person(string id)
        {
            IdCode = id;
        }
    }

   Person p1 = new Person("111");
   Person p2 = new Person("111");

此時 p1 == p2 p1.Equals(p2) false,false

但是如果給Person類重載

  

 public override bool Equals(object obj)
        {
            return IdCode == (obj as Person).IdCode;
        }

此時 p1 == p2 p1.Equals(p2) false,true

(我這邊理解的Equals 和 ==,Equals就是給你用來重載出你想要的 相等性,而==保持其本來的相等性)

再看看

技術分享圖片

看到上圖後註意:GetHashCode不一樣,GetHashCode是幹嘛的。

Object為所有的CLR類型提供了GetHashCode的默認實現。每New一個對象,CLR都會為該對象生成一個固定的整型值,

該整型值在對象的生命周期內不會改變,而對象默認的GetHashCode實現就是對該整型值求HashCode。

還有就是,雖然我們重寫Equals後比較是相等的,但是如果我們存到鍵值的集合中,以該Person作為key,再取,就不是相等的。

因為由於CLR內部會優化這種查找,實際上是根據HashCode來查找Value值。代碼運行的時候首先會調用Person類型的GetHashCode·

由於法線Person沒有實現GetHashCode,所以CLR最終會調用Object的GetHashCode的方法。所以導致這樣的相等的Person查找,是找不到的。

public override bool Equals(object obj)
{
return IdCode == (obj as Person).IdCode;
}

GetHashCode方法哎存在另外一個問題,他永遠只返回一個整型類型,而整型類型的容量顯然無法滿足字符串的容量。

例如:

    string s1 = "2222222222sada";

string s2 = "121122222222222";

    s1.GetHashCode() ;s2.GetHashCode();

為了減少兩個不同類型之間根據字符串產生相同的HashCode的概率,

改進:  

 public override int GetHashCode()
        {
            //不懂為啥這麽寫,但是知道這麽寫就行了,暫時沒必要深究了。
            return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + this.IdCode).GetHashCode();
       //     return this.IdCode.GetHashCode();
        } 

註意:也要記得實現IEquatable<T>.

  

 public bool Equals(Person other)
        {
            return this.IdCode == other.IdCode;
        }

6.正確實現淺拷貝和深拷貝

淺拷貝:

    將對象中的所有字段復制到新的對象(副本)中。其中,值類型字段的值被復制到副本後,

在副本中的修改不會影響到源對象對應的值。而引用類型的字段被復制到副本中的是引用類型的引用,

而不是引用的對象,在副本中對引用類型的字段值做修改會影響到源對象本身。

深拷貝:

    將對象中的所有字段復制到新的對象中,無論是對象的值類型字段,還是引用類型的字段,

都會被重新創建並賦值,對於副本的修改,不會影響到源對象本身。

    實現方式:a.反射 b.反序列化 c.表達式樹。反射進行深拷貝存在問題較多,表達式樹又比較復雜。

我這裏只講述 反序列化進行深拷貝。

無論是深,還是淺拷貝,微軟都建議用 實現 ICloneable接口的方式明確告訴調用者,該類型可以被拷貝。

當然,ICloneable接口只提供了一個聲明為Clone的方法,我們可以根據需求Clone方法實現淺拷貝或深拷貝。

反序列化實現方式

[Serializable]
    class Employee:ICloneable
    {
        public string IDCode { get; set; }
        public int Age;
        public Department department;
        public Object Clone()
        {
            return this.MemberwiseClone();
        }

        /// <summary>
        /// 深拷貝,至於裏面為啥那麽寫,我也就沒深入研究了,大概知道就好了。
        /// </summary>
        /// <returns></returns>
        public Employee DeepClone()
        {
          using (Stream objectStream =new MemoryStream())
          {
              IFormatter formatter = new BinaryFormatter();
              formatter.Serialize(objectStream, this);
              objectStream.Seek(0, SeekOrigin.Begin);
              return formatter.Deserialize(objectStream) as Employee;
          }
        }

        /// <summary>
        /// 淺拷貝 (這個很容易理解)
        /// </summary>
        /// <returns></returns>
        public Employee ShallowClone()
        {
            return Clone() as Employee;
        }
    }

    [Serializable]
    class Department
    {
        public string Name{get;set;}
        public override string ToString()
        {
            return this.Name;
        }

        public Department(string name)
        {
            Name = name;
        }
    }


MemberwiseClone :創建一個新對象,然後將當前對象的非靜態字段復制到該新對象。如果字段是值類型的,

則對該字段執行逐位復制。如果字段是引用類型,則復制引用但不復制引用的對象。因此,原始對象及其本副本引用的是同一對象。

為了實現深度復制,我們就必須便利有相互引用的對象構成的圖,並需要處理其中的循環引用結構。這無疑是復雜的。幸好借助.Net的序列化

和反序列化機制,可以十分簡單的深度clone一個對象。

反序列化深拷貝原理:

首先將對象序列化到內存流中,此時對象和對象引用的所有對象的狀態都被保存到內存中。.Net的序列化機制會自動處理循環引用的情況。

然後將內存流中的狀態信息反序列化到一個新的對象中。這樣一個對象的深度復制就完成了。

註:類前面,以及類中引用的類,一定要加上 Serializable

參考:https://www.cnblogs.com/zhili/p/DeepCopy.html

   https://blog.csdn.net/gooodiuck/article/details/52299229

題外話:這本書真的真的很不錯啊,很適合進階,總結。雖然裏面一些東西自己都是大概知道,但是它剛剛好起了,深入重點,總結的作用。真的很好的一本書,但是淘寶銷量好像很差......

C# 《編寫高質量代碼改善建議》整理&筆記 --(一)基本語言篇