C#規範整理·語言要素

若有不理解,請留言,開始!

1. 正確操做字符串

  • 拼接字符串必定要考慮使用 StringBuilder ,默認長度爲16,實際看狀況設置。
  • StringBuilder本質: 是以非託管方式分配內存。
  • 同時StringFormat方法 內部也是使用StringBuilder進行字符串格式化。

2. 使用默認轉型方法

  1. 類型的轉換運算符 :每一個類型內部都有一個方法(運算符),分爲隱式轉換和顯示轉換。
    本身實現隱式轉換:
puclic static implicit operator Ip(string ip) 
 {
      Ip iptemp=new Ip(ip);
      return iptemp;
 }
  1. 使用類型內置的Parse、TryParse、 ToString、ToDouble、 ToDateTime算法

  2. 使用幫助類提供的方法: System.Convert類、 System.BitConverter類來進行類型的轉換。
  3. 使用CLR支持的類型:父類和子類之間的轉換。數據庫

3. 區別對待強制轉型與as和is

爲了編譯更強壯的代碼,建議更常使用as和is安全

何時使用aside

  • 若是類型之間都上溯到了某個共同的基類,那麼根據此基類進行的轉型(即基類轉型爲子類自己)應該使用as。子類與子類之間的轉型,則應該提供轉換操做符,以便進行強制轉型。
    as操做符永遠不會拋出異常,若是類型不匹配(被轉換對象的運行時類型既不是所轉換的目標類型,也不是其派生類型),或者轉型的源對象爲null,那麼轉型以後的值也爲null。

何時使用is函數

  • as操做符有一個問題,即它不能操做基元類型。若是涉及基元類型的算法,就須要經過is轉型前的類型來進行判斷,以免轉型失敗。

4.TryParse比Parse好

這個確定好,不說了。安全ui

5.使用int?來確保值類型也能夠爲null

基元類型爲何須要爲null?考慮兩個場景:this

  1. 數據庫支持整數可爲空
  2. 數據在傳輸過程當中存在丟失問題,致使傳過來的值爲null

寫法: int?i=null;code

語法T?是Nullable<T>的簡寫,二者能夠相互轉換。能夠爲null的類型表示其基礎值類型正常範圍內的值再加上一個null值。例如,Nullable<Int32>,其值的範圍爲-2 147 483 648~2 147 483 647,再加上一個null值。orm

?常常和??配合使用,好比:對象

int?i=123;
 int j=i??0;

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

使用const的理由只有一個,那就是效率。之因此說const變量的效率高,是由於通過編譯器編譯後,咱們在代碼中引用const變量的地方會用const變量所對應的實際值來代替。好比: const=100, const和100被使用的時候是等價,const自帶static光圈。
const和readonly的本質區別以下:

  1. const是編譯期常量,readonly是運行期常量
  2. const只能修飾基元類型、枚舉類型或字符串類型,readonly沒有限制。
  • 注意:在構造方法內,能夠屢次對readonly賦值。即在初始化的時候。

7.將0值做爲枚舉的默認值

容許使用的枚舉類型有byte、sbyte、short、ushort、int、uint、long和ulong。應該始終將0值做爲枚舉類型的默認值。不過,這樣作不是由於容許使用的枚舉類型在聲明時的默認值是0值,而是有工程上的意義。
既然枚舉類型從0開始,這樣能夠避免一個星期多出來一個0值。

8.避免給枚舉類型的元素提供顯式的值

不要給枚舉設定值。有時候有某些增長的須要,會爲枚舉添加元素,在這個時候,就像咱們爲枚舉增長元素ValueTemp同樣,極有可能會一不當心增長一個無效值。

9.習慣重載運算符

好比:Salary familyIncome=mikeIncome+roseIncome; 閱讀一目瞭然。經過使用opera-tor關鍵字定義靜態成員函數來重載運算符,讓開發人員能夠像使用內置基元類型同樣使用該類型。

10.建立對象時須要考慮是否實現比較器

有特殊須要比較的時候就考慮。集合排序比較經過linq 也能夠解決。

11.區別對待==和Equals

不管是操做符「==」仍是方法「Equals」,都傾向於表達這樣一個原則:

  1. 對於值類型,若是類型的值相等,就應該返回True。
  2. 對於引用類型,若是類型指向同一個對象,則返回True。

    注意 

  • 因爲操做符「==」和「Equals」方法從語法實現上來講,均可以被重載爲表示「值相等性」和「引用相等性」。因此,爲了明確有一種方法確定比較的是「引用相等性」,FCL中提供了Object.ReferenceEquals方法。該方法比較的是:兩個示例是不是同一個示例。
  • 對於string這樣一個特殊的引用類型,微軟以爲它的現實意義更接近於值類型,因此,在FCL中,string的比較被重載爲針對「類型的值」的比較,而不是針對「引用自己」的比較。

12.重寫Equals時也要重寫GetHashCode

  • 除非考慮到自定義類型會被用做基於散列的集合的鍵值;不然,不建議重寫Equals方法,由於這會帶來一系列的問題。
    集合找到值的時候本質上是先去 查找HashCode,而後才查找該對象來比較Equals

注意
重寫Equals方法的同時,也應該實現一個類型安全的接口IEquatable<T>,好比 :class Person:IEquatable

13.爲類型輸出格式化字符串

有兩種方法能夠爲類型提供格式化的字符串輸出。

  1. 一種是意識到類型會產生格式化字符串輸出,因而讓類型繼承接口IFormattable。這對類型來講,是一種主動實現的方式,要求開發者能夠預見類型在格式化方面的要求。
  2. 更多的時候,類型的使用者需爲類型自定義格式化器,這就是第二種方法,也是最靈活多變的方法,能夠根據需求的變化爲類型提供多個格式化器。

一個典型的格式化器應該繼承接口IFormatProvider和ICustomFomatter

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

淺拷貝 

將對象中的全部字段複製到新的對象(副本)中。其中,值類型字段的值被複制到副本中後,在副本中的修改不會影響到源對象對應的值。而引用類型的字段被複制到副本中的是引用類型的引用,而不是引用的對象,在副本中對引用類型的字段值作修改會影響到源對象自己。

深拷貝 

一樣,將對象中的全部字段複製到新的對象中。不過,不管是對象的值類型字段,仍是引用類型字段,都會被從新建立並賦值,對於副本的修改,不會影響到源對象自己。

不管是淺拷貝仍是深拷貝,微軟都建議用類型繼承IClone-able接口的方式明確告訴調用者:該類型能夠被拷貝。固然,ICloneable接口只提供了一個聲明爲Clone的方法,咱們能夠根據需求在Clone方法內實現淺拷貝或深拷貝。

  • 一個簡單的淺拷貝的實現代碼以下所示:
class Employee:ICloneable
 {    
       public string IDCode {get;set;}   
       public int Age {get;set;  }
       public Department Department{get;set;}    

    #region ICloneable成員 
    public object Clone() 
   {       
    return this.MemberwiseClone(); 
    } 

   #endregion
 
}

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

注意到Employee的IDCode屬性是string類型。理論上string類型是引用類型,可是因爲該引用類型的特殊性(不管是實現仍是語義),Object.MemberwiseClone方法仍舊爲其建立了副本。也就是說,在淺拷貝過程,咱們應該將字符串當作是值類型。

  • 一個簡單的深拷貝實現樣例以下(建議使用序列化的形式來進行深拷貝)
class Employee:ICloneable
{    
 public string IDCode{get;set;}   
 public int Age{get;set;}    
 public Department Department{get;set;}   

#region ICloneable成員    
public object Clone()    
{        
 using(Stream objectStream=new MemoryStream())        
{            
 IFormatter formatter=new BinaryFormatter();            
 formatter.Serialize(objectStream,this);            
 objectStream.Seek(0,SeekOrigin.Begin);            
 return formatter.Deserialize(objectStream)as Employee;        
}   
}   
 #endregion}

同時實現深拷貝和淺拷貝

因爲接口ICloneable只有一個模棱兩可的Clone方法,因此,若是要在一個類中同時實現深拷貝和淺拷貝,只能由咱們本身實現兩個額外的方法,聲明爲DeepClone和Shallow。Em-ployee的最終版本看起來應該像以下的形式:

[Serializable]
class Employee:ICloneable
{   
 public string IDCode{get;set;}    
 public int Age{get;set;}    
 public Department Department{get;set;}   
 #region ICloneable成員    
 public object Clone()   
 {       
  return this.MemberwiseClone();   
  }    

#endregion   
  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;       
    }   
 }  

  public Employee ShallowClone()   
  {       
     return Clone()as Employee;    
   }}

14.利用dynamic來簡化反射實現

dynamic是Framework 4.0的新特性。dynamic的出現讓C#具備了弱語言類型的特性。編譯器在編譯的時候再也不對類型進行檢查,編譯器默認dynamic對象支持開發者想要的任何特性。
好比,即便你對GetDynamicObject方法返回的對象一無所知,也能夠像以下這樣進行代碼的調用,編譯器不會報錯:

dynamic dynamicObject=GetDynamicObject();
Console.WriteLine(dynamicObject.Name);
Console.WriteLine(dynamicObject.SampleMethod());

固然,若是運行時dynamicObject不包含指定的這些特性(如上文中帶返回值的方法SampleMethod),運行時程序會拋出一個RuntimeBinderException異常:「System.Dynamic.ExpandoObject」未包含「Sam-pleMethod」的定義。

var與dynamic有巨大的區別

  • var是編譯器的語法糖
  • dynamic是運行時解析,在編譯期時,編譯器不對其作任何檢查。

反射使用

  • 不使用dynamic方式
DynamicSample  dynamicSample=new  DynamicSample();
var addMethod=typeof(DynamicSample).GetMethod("Add");
int re=(int)addMethod.Invoke(dynamicSample,new object[] {1,2});
  • 使用dynamic方式
dynamic dynamicSample2=new DynamicSample();
int re2=dynamicSample2.Add(1,2);

//在使用dynamic後,代碼看上去更簡潔了,而且在可控的範圍內減小了一次拆箱的機會。經驗證,頻繁使用的時候,消耗時間更少

建議:始終使用dynamic來簡化反射實現。

總結

在大部分應用狀況下,「效率」並無那麼高的地位,靈活性更重要。在部分狀況下,「靈活性」並無那麼高的地位,效率最重要。

相關文章
相關標籤/搜索