9、習慣重載運算子
在構建自己的類型時,始終應該考慮是否可以使用運算子多載
10、建立對象時需要考慮是否實現比較子
如果需要排序,有兩種比較子實現
class FirstType : IComparable<FirstType>{ public string name; public int age; public FirstType(int age) { name = "aa"; this.age = age; } public int CompareTo(FirstType other) { return other.age.CompareTo(age); }}static void Main(string[] args){ FirstType f1 = new FirstType(3); FirstType f2 = new FirstType(5); FirstType f3 = new FirstType(2); FirstType f4 = new FirstType(1); List<FirstType> list = new List<FirstType> { f1,f2,f3,f4 }; list.Sort(); foreach (var item in list) { Console.WriteLine(item); }}
或者第二種
class Program : IComparer<FirstType>{ static void Main(string[] args) { FirstType f1 = new FirstType(3); FirstType f2 = new FirstType(5); FirstType f3 = new FirstType(2); FirstType f4 = new FirstType(1); List<FirstType> list = new List<FirstType> { f1,f2,f3,f4 }; list.Sort(new Program()); foreach (var item in list) { Console.WriteLine(item); } } int IComparer<FirstType>.Compare(FirstType x, FirstType y) { return x.age.CompareTo(y.age); }}
它調用的是Program的Compare方法
11、區別對待==和Equals
無論是== 還是Equals:
對於實值型別,如果類型的值相等,則返回True
對於參考型別,如果類型指向同一個對象,則返回True
且他們都可以被重載
對於string這樣一個特殊的引用類,微軟可能認為它的現實意義更傾向於一個實值型別,所以在FCL(Framework Class Library)中string的比較被重載為值比較,而不是針對引用本身
從設計上來說,很多參考型別會存在類似於string類型比較相近的情況,如人,他的社會安全號碼相同,則我們就認為是一個人,這個時候就需要重載Equals方法,
一般來說,對於參考型別,我們要定義值相等的特性,應該僅僅重寫Equals方法,同時讓==表示引用相等,這樣我們想比較哪個都是可以的
由於操作符“==”和“Equals”都可以被重載為“值相等”和“引用相等”,所以為了明確,FCL提供了object.ReferenceEquals(); 來比較兩個執行個體是否為同一個引用
12、重寫Equals時也要重寫GetHashCode
字典中判斷ContainsKey的時候使用的是key類型的HashCode,所以我們想都使用類型中的某個值來作為判斷條件,就需要重新GetHashCode,當然還有其他使用HashCode來作為判斷是否相等的,如果我們不重寫,可以會產生其他的效果
public override int GetHashCode(){ //這樣寫是為了減少HashCode重複的機率,至於為什麼這樣寫我也不清楚。。 記著就行 return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + age).GetHashCode();}
重寫Equals方法的同時,也應該事先一個型別安全的介面IEquatable<T> ,所以重寫Equals的最終版本應該是
class FirstType : IEquatable<FirstType>{ public string name; public int age; public FirstType(int age) { name = "aa"; this.age = age; } public override bool Equals(object obj) { return age.Equals(((FirstType)obj).age); } public bool Equals(FirstType other) { return age.Equals(other.age); } public override int GetHashCode() { return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + age).GetHashCode(); }}
13、為類型輸出格式化字串
class Person : IFormattable{ public override string ToString() { return "Default Hello"; } public string ToString(string format, IFormatProvider formatProvider) { switch (format) { case "Chinese": return "你好"; case "English": return "Hello"; } return "helo"; }}
static void Main(string[] args){ Person p1 = new Person(); Console.WriteLine(p1); Console.WriteLine(p1.ToString("Chinese",null)); Console.WriteLine(p1.ToString("English", null));}
當繼承了IFormattable介面後,可以在ToString裡面穿參數來調用不同的ToString,上面的預設ToString就不會調用到了
具體還有一個IFormatProvider介面,我還沒研究
14、正確實現淺拷貝和深拷貝
無論是深拷貝還是淺拷貝,微軟都見識用類型繼承ICloneable介面的方式來明確告訴調用者:該類型可以被拷貝
//記得在類前添加[Serializable]的標誌[Serializable]class Person : ICloneable{ public string name; public Child child; public object Clone() { //淺拷貝 return this.MemberwiseClone(); } /// <summary> /// 深拷貝 /// 我也不清楚為什麼這樣寫 /// </summary> /// <returns></returns> public Person DeepClone() { using (Stream objectStream = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(objectStream, this); objectStream.Seek(0, SeekOrigin.Begin); return formatter.Deserialize(objectStream) as Person; } }}[Serializable]class Child{ public string name; public Child(string name) { this.name = name; } public override string ToString() { return name; }}
淺拷貝:
輸出的p1.child.name 是更改過的p2的child.name
深拷貝:
輸出的是原來的
深拷貝是對於參考型別來說,理論上string類型是參考型別,但是由於該參考型別的特殊性,Object.MemberwiseClone仍然為其建立了副本,也就是說在淺拷貝過程中,我們應該將字串看成是實值型別
15、使用dynamic來簡化反射實現
static void Main(string[] args){ //使用反射 Stopwatch watch = Stopwatch.StartNew(); Person p1 = new Person(); var add = typeof(Person).GetMethod("Add"); for (int i = 0; i < 1000000; i++) { add.Invoke(p1, new object[] { 1, 2 }); } Console.WriteLine(watch.ElapsedTicks); //使用dynamic watch.Reset(); watch.Start(); dynamic d1 = new Person(); for (int i = 0; i < 1000000; i++) { d1.Add(1, 2); } Console.WriteLine(watch.ElapsedTicks);}
可以看出使用dynamic會比使用反射寫出來的代碼美觀且簡潔,而且多次啟動並執行效率也會更高,因為dynamic第一次運行後會緩衝起來
幾乎相差了10倍
但是反射如果次數較少效率會更高
這個是運行了100次的結果
但是很多時候效率不是必要的,始終推薦使用dynamic來簡化反射的實現
相關文章:
C#學習記錄:編寫高品質代碼改善整理建議1-3
C#學習記錄:編寫高品質代碼改善整理建議4-8