歡迎光臨
每天分享高質量文章

C# 規範整理·語言要素

作者:天空的湛藍

鏈接:https://www.cnblogs.com/zhan520g/p/11014918.html

1、正確操作字串

 

  • 拼接字串一定要考慮使用StringBuilder ,預設長度為16,實際看情況設置。

  • StringBuilder本質:是以非托管方式分配記憶體。

  • 同時StringFormat方法內部也是使用StringBuilder進行字串格式化。

 

2、使用預設轉型方法

 

型別的轉換運算子 :每個型別內部都有一個方法(運算子),分為隱式轉換和顯示轉換。

自己實現隱式轉換:

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

 

什麼時候使用as

 

如果型別之間都上溯到了某個共同的基類,那麼根據此基類進行的轉型(即基類轉型為子類本身)應該使用as。子類與子類之間的轉型,則應該提供轉換運算子,以便進行強制轉型。

 

as運算子永遠不會丟擲異常,如果型別不匹配(被轉換物件的運行時型別既不是所轉換的標的型別,也不是其派生型別),或者轉型的源物件為null,那麼轉型之後的值也為null。

 

什麼時候使用is

 

as運算子有一個問題,即它不能操作基元型別。如果涉及基元型別的演算法,就需要通過is轉型前的型別來進行判斷,以避免轉型失敗。

 

4、TryParse比Parse好

 

這個肯定好,不說了。安全

 

5、使用int?來確保值型別也可以為null

 

基元型別為什麼需要為null?考慮兩個場景:

 

  • 資料庫支持整數可為空

     

  • 資料在傳輸過程中存在丟失問題,導致傳過來的值為null

 

寫法:int ? i=null;

 

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

 

?經常和??配合使用,比如:

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

 

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

 

使用const的理由只有一個,那就是效率。之所以說const變數的效率高,是因為經過編譯器編譯後,我們在代碼中取用const變數的地方會用const變數所對應的實際值來代替。比如:const=100, const和100被使用的時候是等價,const自帶static光圈。

 

const和readonly的本質區別如下:

 

  • const是編譯期常量,readonly是運行期常量

  • 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”,都傾向於表達這樣一個原則:

 

  • 對於值型別,如果型別的值相等,就應該傳回True。

  • 對於取用型別,如果型別指向同一個物件,則傳回True。

 

註意 

 

  • 由於運算子“==”和“Equals”方法從語法實現上來說,都可以被多載為表示“值相等性”和“取用相等性”。所以,為了明確有一種方法肯定比較的是“取用相等性”,FCL中提供了Object.ReferenceEquals方法。該方法比較的是:兩個示例是否是同一個示例。

  • 對於string這樣一個特殊的取用型別,微軟覺得它的現實意義更接近於值型別,所以,在FCL中,string的比較被多載為針對“型別的值”的比較,而不是針對“取用本身”的比較。

 

12、重寫Equals時也要重寫GetHashCode

 

除非考慮到自定義型別會被用作基於散列的集合的鍵值;否則,不建議重寫Equals方法,因為這會帶來一系列的問題。

 

集合找到值的時候本質上是先去 查找HashCode,然後才查找該物件來比較Equals

 

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

 

13、為型別輸出格式化字串

 

有兩種方法可以為型別提供格式化的字串輸出。

 

  • 一種是意識到型別會產生格式化字串輸出,於是讓型別繼承接口IFormattable。這對型別來說,是一種主動實現的方式,要求開發者可以預見型別在格式化方面的要求。

  • 更多的時候,型別的使用者需為型別自定義格式化器,這就是第二種方法,也是最靈活多變的方法,可以根據需求的變化為型別提供多個格式化器。

 

一個典型的格式化器應該繼承接口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;
   }}

 

15、利用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來簡化反射實現。

 

總結

 

在大部分應用情況下,“效率”並沒有那麼高的地位,靈活性更重要。在部分情況下,“靈活性”並沒有那麼高的地位,效率最重要。

赞(0)

分享創造快樂