**************************************************************************
該書在線閱讀:編寫高質量代碼:改善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 字段可以在聲明或構造函數中初始化。(對於實例字段,在包含字段聲明的類的實例構造函數中;或者,對於靜態字段,在包含字段聲明的類的靜態構造函數中)
注意:
- const本身是編譯期常量,所以就是static的,加上static會編譯錯誤。
- readonly靈活性大於const,但性能卻略低於const(極小)。所以推薦盡量使用readonly。
- readonly變量是運行時變量,只能在聲明或構造函數中初始化(能在構造函數中多次賦值),在其他地方“不可以更改”。
“不可以更改”有兩層含義:
-
- 對於值類型變量,值本身不可以改變(readonly,只讀)
- 對於引用類型變量,引用本身(相當於指針)不可改變,但是其成員可被改變。
Sample2 sample2 = new Sample2(new Student() { Age = 10 }); sample2.ReadOnlyValue.Age = 20; //成功
-----------------------------------------
建議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>接口,實現默認比較器。如果需要其他比較可以如下例子中創建非默認的比較器。

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。

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
-----------------------------------------------------------
例子:

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。
代碼如下:

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。如果類型本身沒有提供格式化輸出的功能,這個時候,格式化器就派上了用場。格式化器的好處就是可以根據需求的變化,隨時增加或者修改它。

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; } 17 public string FirstName { get; set; } 18 public string LastName { get; set; } 19 } 20 21 class PersonFomatter : IFormatProvider, ICustomFormatter 22 { 23 24 #region IFormatProvider 成員 25 26 public object GetFormat(Type formatType) 27 { 28 if (formatType == typeof(ICustomFormatter)) 29 return this; 30 else 31 return null; 32 } 33 34 #endregion 35 36 #region ICustomFormatter 成員 37 38 public string Format(string format, object arg, IFormatProvider formatProvider) 39 { 40 Person person = arg as Person; 41 if (person == null) 42 { 43 return string.Empty; 44 } 45 46 switch (format) 47 { 48 case "Ch": 49 return string.Format("{0} {1}", person.LastName, person.FirstName); 50 case "En": 51 return string.Format("{0} {1}", person.FirstName, person.LastName); 52 case "ChM": 53 return string.Format("{0} {1} : {2}", person.LastName, person.FirstName, person.IDCode); 54 default: 55 return string.Format("{0} {1}", person.FirstName, person.LastName); 56 } 57 } 58 59 #endregion 60 }
結合以上兩個版本,可以得出最終版。(至於選擇哪個版本,看具體需求確定)

1 static void Main(string[] args) 2 { 3 Person person = new Person() { FirstName = "Jessica", LastName = "Hu", IDCode = "NB123" }; 4 Console.WriteLine(person.ToString()); 5 PersonFomatter pFormatter = new PersonFomatter(); 6 //第一類格式化輸出語法 7 Console.WriteLine(pFormatter.Format("Ch", person, null)); 8 Console.WriteLine(pFormatter.Format("En", person, null)); 9 Console.WriteLine(pFormatter.Format("ChM", person, null)); 10 //第二類格式化輸出語法,也更簡潔 11 Console.WriteLine(person.ToString("Ch", pFormatter)); 12 Console.WriteLine(person.ToString("En", pFormatter)); 13 Console.WriteLine(person.ToString("ChM", pFormatter)); 14 15 } 16 } 17 18 class Person : IFormattable 19 { 20 public string IDCode { get; set; } 21 public string FirstName { get; set; } 22 public string LastName { get; set; } 23 24 //實現接口IFormattable的方法ToString 25 public string ToString(string format, IFormatProvider formatProvider) 26 { 27 switch (format) 28 { 29 case "Ch": 30 return this.ToString(); 31 case "En": 32 return string.Format("{0} {1}", FirstName, LastName); 33 default: 34 return this.ToString(); 35 ICustomFormatter customFormatter = formatProvider as ICustomFormatter; 36 if (customFormatter == null) 37 { 38 return this.ToString(); 39 } 40 return customFormatter.Format(format, this, null); 41 42 } 43 } 44 45 //重寫Object.ToString() 46 public override string ToString() 47 { 48 return string.Format("{0} {1}", LastName, FirstName); 49 } 50 } 51 52 class PersonFomatter : IFormatProvider, ICustomFormatter 53 { 54 55 #region IFormatProvider 成員 56 57 public object GetFormat(Type formatType) 58 { 59 if (formatType == typeof(ICustomFormatter)) 60 return this; 61 else 62 return null; 63 } 64 65 #endregion 66 67 #region ICustomFormatter 成員 68 69 public string Format(string format, object arg, IFormatProvider formatProvider) 70 { 71 Person person = arg as Person; 72 if (person == null) 73 { 74 return string.Empty; 75 } 76 77 switch (format) 78 { 79 case "Ch": 80 return string.Format("{0} {1}", person.LastName, person.FirstName); 81 case "En": 82 return string.Format("{0} {1}", person.FirstName, person.LastName); 83 case "ChM": 84 return string.Format("{0} {1} : {2}", person.LastName, person.FirstName, person.IDCode); 85 default: 86 return string.Format("{0} {1}", person.FirstName, person.LastName); 87 } 88 } 89 90 #endregion 91 }
--------------------------------------------
建議14:正確實現淺拷貝和深拷貝 / 36
--------------------------------------------
為對象創建副本的技術稱為拷貝(也叫克隆)。將拷貝分為淺拷貝和深拷貝。
- 共同點:將對象中的所有字段復制到新的對象(副本)中。而且,值類型字段的值被復制到副本中后,在副本中的修改不會影響到源對象對應的值。
- 不同點:引用類型淺拷貝復制的是引用類型的引用。即修改副本引用類型的值即是修改源對象的值。引用類型深拷貝而是拷貝整個引用對象,如同值類型。
- 注:string類型盡管是引用類型,但因性質特殊,所以按值類型處理。
拷貝建議通過繼承IConeable接口的方式實現。但是該接口只有一個Clone的方法。所以淺拷貝和深拷貝可以自己定義。
實現深拷貝的方法有很多,比如手動拷貝每個字段的方法,但是不推薦,該方法不靈活也容易出錯。
建議使用序列化的形式進行深拷貝。

1 [Serializable] 2 class Employee : ICloneable 3 { 4 public string IDCode { get; set; } 5 public int Age { get; set; } 6 public Department Department { get; set; } 7 8 #region ICloneable 成員 9 10 public object Clone() 11 { 12 return this.MemberwiseClone(); 13 } 14 15 #endregion 16 17 public Employee DeepClone() 18 { 19 using (Stream objectStream = new MemoryStream()) 20 { 21 IFormatter formatter = new BinaryFormatter(); 22 formatter.Serialize(objectStream, this); 23 objectStream.Seek(0, SeekOrigin.Begin); 24 return formatter.Deserialize(objectStream) as Employee; 25 } 26 } 27 28 public Employee ShallowClone() 29 { 30 return Clone() as Employee; 31 } 32 }
--------------------------------------------------
建議15:使用dynamic來簡化反射實現 / 40
--------------------------------------------------
dynamic是Framework4.0的新特性。dynamic的出現讓C#具有了弱語言類型的特性,編譯器在編譯的時候不再對類型進行檢查,編譯器默認dynamic對象支持開發者想要的任何特性。
利用dynamic的這個特性,可以簡化C#中的反射語法,大大提高效率。
通過的例子比較性能:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int times = 1000000; 6 DynamicSample reflectSample = new DynamicSample(); 7 var addMethod = typeof(DynamicSample).GetMethod("Add"); 8 Stopwatch watch1 = Stopwatch.StartNew(); 9 for (var i = 0; i < times; i++) 10 { 11 addMethod.Invoke(reflectSample, new object[] { 1, 2 }); 12 } 13 Console.WriteLine(string.Format("反射耗時:{0} 毫秒", watch1.ElapsedMilliseconds)); 14 dynamic dynamicSample = new DynamicSample(); 15 Stopwatch watch2 = Stopwatch.StartNew(); 16 for (int i = 0; i < times; i++) 17 { 18 dynamicSample.Add(1, 2); 19 } 20 Console.WriteLine(string.Format("dynamic耗時:{0} 毫秒", watch2.ElapsedMilliseconds)); 21 22 DynamicSample reflectSampleBetter = new DynamicSample(); 23 var addMethod2 = typeof(DynamicSample).GetMethod("Add"); 24 var delg = (Func<DynamicSample, int, int, int>)Delegate.CreateDelegate(typeof(Func<DynamicSample, int, int, int>), addMethod2); 25 Stopwatch watch3 = Stopwatch.StartNew(); 26 for (var i = 0; i < times; i++) 27 { 28 delg(reflectSampleBetter, 1, 2); 29 } 30 Console.WriteLine(string.Format("優化的反射耗時:{0} 毫秒", watch3.ElapsedMilliseconds)); 31 } 32 } 33 34 public class DynamicSample 35 { 36 public string Name { get; set; } 37 38 public int Add(int a, int b) 39 { 40 return a + b; 41 } 42 }
從結果來看,優化的反射實現,其效率和dynamic在一個數量級上。可是它帶來了效率,卻犧牲了代碼的整潔度,這種實現在我看來是得不償失的。所以,現在又了dynamic類型,建議大家:
始終使用dynamic來簡化反射實現。
注:以上是原文的例子,但是個人感覺有些問題,作者只是測試了調用的效率。如果反射一次,多次調用的話,作者的例子可以參考。
如果是反射一次,調用一次的模式的話,第三個優化反射中委托的創建也應該放進循環中,同時反射(GetMethod)也應該放進循環中。
以下是本人測試性能的例子:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Diagnostics; 6 7 namespace TestReflection 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 int times = 1000000; 14 var sw = Stopwatch.StartNew(); 15 var sample = new Sample(); 16 17 //反射調用 18 for (int i = 0; i < times; i++) 19 { 20 typeof(Sample).GetMethod("Add").Invoke(sample, new object[] { 1, 2 }); 21 } 22 sw.Stop(); 23 Console.WriteLine("Invoke:{0}毫秒", sw.ElapsedMilliseconds.ToString()); 24 25 //dynamic調用 26 dynamic ds = new Sample(); 27 sw.Restart(); 28 for (int i = 0; i < times; i++) 29 { 30 ds.Add(1, 1); 31 } 32 sw.Stop(); 33 Console.WriteLine("dynamic:{0}毫秒", sw.ElapsedMilliseconds.ToString()); 34 35 //轉換成接口調用:如果被反射的類實現了統一接口,則可轉換為接口調用該方法 36 sw.Restart(); 37 for (int i = 0; i < times; i++) 38 { 39 (sample as Ibase).Add(1, 2); 40 } 41 sw.Stop(); 42 Console.WriteLine("Interface:{0}毫秒", sw.ElapsedMilliseconds.ToString()); 43 44 //反射后委托調用 45 sw.Restart(); 46 for (int i = 0; i < times; i++) 47 { 48 var method = typeof(Sample).GetMethod("Add"); 49 var delg = (Func<Sample, int, int, int>)Delegate.CreateDelegate(typeof(Func<Sample, int, int, int>), method); 50 delg(sample, 1, 1); 51 } 52 sw.Stop(); 53 Console.WriteLine("Delegate&Reflection:{0}毫秒", sw.ElapsedMilliseconds.ToString()); 54 } 55 } 56 57 interface Ibase 58 { 59 int Add(int x, int y); 60 } 61 62 class Sample : Ibase 63 { 64 public int Add(int x, int y) 65 { 66 return x + y; 67 } 68 } 69 }
由上面的結果可見:
個人建議:
- 如果這些需反射的類都實現了同一個接口,即需反射的成員繼承同一個接口,那么將對象轉換為接口,調用接口的成員,無需使用反射。
- 這些類沒有實現同一接口,那么優先采用dynamic方式。
- 如果Framework版本低於4.0,那么只能采用反射的方式,至於用Invoke還是delegate,如果反射和調用次數相差不大,建議用Invoke,如果調用次數遠多於反射,那么建議用委托。
SamWang
2012-05-22
作者:SamWang
出處:http://wangshenhe.cnblogs.com/
本文版權歸作者和博客園共有,歡迎圍觀轉載。轉載時請您務必在文章明顯位置給出原文鏈接,謝謝您的合作。