深入理解C#:編程技巧總結(一)

来源:http://www.cnblogs.com/susufufu/archive/2017/01/08/6263122.html
-Advertisement-
Play Games

原創文章,轉載請註明出處! 以下總結參閱了:MSDN文檔、《C 高級編程》、《C 本質論》等資料,如有不正確的地方,請幫忙及時指出!以免誤導! 1.實現多態性的兩種方式:繼承抽象類、實現介面 其實就是協變的應用,通過把對象向上轉型為基類或介面類型,對它調用成員,可實現多態性,即運行時調用的是對應對象 ...


原創文章,轉載請註明出處! 以下總結參閱了:MSDN文檔、《C#高級編程》、《C#本質論》、前輩們的博客等資料,如有不正確的地方,請幫忙及時指出!以免誤導!

1..實現多態性的兩種方式:繼承抽象類、實現介面

其實就是協變的應用,通過把對象向上轉型為基類或介面類型,對它調用成員,可實現多態性,即運行時調用的是對應對象的實現版本成員。這兩種方式的區別:

  • 繼承抽象類:會用掉唯一1次的繼承機會,但可以繼承任何成員(包括欄位),自由度高
  • 實現介面:必須實現所有成員,不能包含欄位,但可以實現多個介面
  • 抽象類可以提供成員的具體實現,而介面只負責聲明,不能提供任何實現代碼

註意:

  • 介面一旦被定義就不應該再被改變,否則所有實現該介面的類型都必須跟著修改。
  • 而抽象類則可以隨時添加新的成員,不影響他的子類,還能提供新的額外功能。

多態性示例:(協變與逆變)

//可以返回Stream的任何子類類型
Stream Method1(bool boo)
{ }
//可以接收Stream的任何子類類型的參數
void Method2(Stream stream)
{ }

2.不要創建可變的值類型(結構、枚舉),若要改變,請用一個方法來返回一個新實例。要時刻註意頻繁的裝箱與拆箱對性能的影響

3.僅在能一眼看出變數的類型時,才使用var聲明

4.定義值類型時,它的大小不要超過16位元組,否則影響性能(頻繁複制時),要麼改為使用引用類型,要麼讓它按ref引用傳遞

5.值類型數組之間不能直接互相轉換,可以通過一次中間轉換為Array來達到目的,如:

(int[])(Array)new uint[32]
但應註意可能在不同的CLR實現中表現不同!

6.數組與List

  • 如果元素數量固定,且不涉及轉型,則使用數組效率更高。
  • 在元素數量可能發生變化的情況下,就不應該使用數組,而應該使用List
  • 無論是數組還是List,元素個數也不能太多,避免成為占用記憶體超過85000位元組的大對象,因為大對象將會被分配到單獨的堆進行處理,在回收大對象時效率較低。

7.字元串操作

  • 字元串字面量、字元串常量,直接用"+"相連效率高,因為:string str = "srf"+"ttt"+"ccc";會直接編譯成string str = "srftttccc";,同樣適用於字元串常量。
  • 儘量避免對變數的裝箱:字元串+變數,較好的做法是:字元串+變數.ToString()
  • 頻繁操作字元串時用StringBuilder,並制定足夠大的容量,而string.Format("{0}{1}{2}",str1,str2,str3);內部也是用StringBuilder

8.類型轉換

字元串轉其它基元類型:

  • 預設十進位:用Parse()、TryParse(),如:int.TryParse("24");,其中TryParse效率更高
  • 指定基數進位形式來解析:Convert.ToInt32("0xFF",16);
  • 從位元組數組中提取一段,轉為基元類型:BitConvert.ToInt32(Byte[] arr, int startIndex);

自定義類型之間的強制轉換:
從基類強制轉換為子類時,安全的做法是使用"as",若目標為null或類型不相容轉換失敗,均會返回null,而不會引發錯誤,如基類Person,它的子類Man、Women

Person person = new Man();//自動向基類隱式轉換,但person的運行時類型仍為Man
Women women = (Women)person; //錯誤
Women women = person as Women; //women為null ,因為男人不能轉換為女人

但需註意"as"只能應用於引用類型或可為null類型。若目標可能為基元類型,則應該通過"is"操作符來過濾

if(!(person is int))
{
    Women women = person as Women;
}

子類與子類之間的橫向轉換,應該定義轉換操作符(關鍵字implicit、explicit)

9.獲取一個可空類型Nullable的值,安全簡單的做法是用"??",如 int j = i ?? 0;,普通做法:

if(i.HasValue()) { int j = i.Value; }

10.常量const和只讀欄位readonly的區別:

  • const是編譯期常量,它總是靜態的,編譯時直接用實際值填充。而readonly是一個運行時常量。
  • const只能修飾基元類型、枚舉類型、字元串類型,而readonly沒有限制。
  • const一經聲明就必須初始化,且之後就無法再改變。而readonly可顯式初始化,也可不初始化,它的值可以通過構造函數來改變(即每個實例有自己的readonly只讀欄位值)
    註意:除了構造函數之外,都無法改變readonly的值,對於引用類型是無法改變它的引用,即它只能引用同一對象。但該對象本身是可以被修改的。

11.枚舉類型

  • 枚舉類型可以為從byte到ulong的基元類型,定義枚舉時應該始終為它定義一個零值,因為聲明一個枚舉變數而未初始化時的預設值將是0
  • 除了0值,要麼都不為成員顯式賦值,要麼就全部賦值(如應用了Flags特性的標誌枚舉),否則未賦值的成員將等於它前一個成員的值加1,因為枚舉成員的值預設是按順序逐個加1
  • 對枚舉應用[Flags]特性,可以定義一個標誌枚舉,它的成員值通常初始化為2的次冪,之後就可以通過按位運算來判斷、合併枚舉成員了。
  • 定義一個枚舉來專門負責表示狀態的信息,這樣使代碼更易理解。如用枚舉成員on、off來代替true、false或0、1

12.如果需要,應該為類型重載常用的運算符和比較運算符,如重載">"以實現person1>person2

13.若該類型有泛型版本,則應該使用泛型版本,因為泛型類型效率更高(避免了裝箱、拆箱、類型轉換)

14.相等性

  • 值類型:對於值相等的兩個值類型變數A、B,"A==B"和"A.Equals(B)"都返回true,而Object.ReferenceEquals(A,B)總是返回false。
  • 引用類型:Object.ReferenceEquals(A,B)比較的是引用是否相等,而預設的A.Equals(B)也是比較的引用,需要重載Equals()方法來實現引用類型之間的"值相等性比較"(如:當person1.ID == person2.ID時,person1.Equals(person2)返回true,來表示他們相等)
  • 註意1:重寫了Equals()方法,最好也一起重寫GetHashCode()方法,因為對於不同的對象,預設的GetHashCode()返回的值將永遠不同,而若把對象作為Dictionary的TKey時,根據TKey取值時,會根據對象的HashCode來比較。所以需要重新GetHashCode(),使得Equals()方法返回true時,GetHashCode()返回的值也相同,這樣字典才能正常工作。
  • 註意2:重寫了Equals()、GetHashCode()方法,同時也應該實現IEquatable介面,該介面的成員bool Equals(T t1)比Object的Equals(object obj)類型更安全、更高效。
  • 註意3:對於字元串,雖然它也是對象,但當兩個字元串所包含的字面值一樣時,運行時將只在記憶體中創建一個該字面值的字元串對象,也就是說所有字面值一樣的字元串對象都將引用同一個地址。

15.ToString()方法

應該總是為自定義類型重寫Object的ToString()方法,最好還要實現IFormattable介面,該介面的ToString(string format, IFormatProvider formatProvider)提供了根據參數來輸出特定的格式化形式。如:

public string ToString(string format, IFormatProvider formatProvider)
{
    switch(format)
    {
        case "CH":
                return this.ToString();
        case "EN":
                return string.Format("{0}{1}",FirstName,LastName);
        ......
    }
}
//調用
Console.WriteLine(person.ToString("EN",null));

16.對象的淺拷貝與深拷貝

  • 淺拷貝:使用Object基類的實例方法MemberwiseClone()來獲得對象的一個淺拷貝副本。
  • 深拷貝:通過系列化與反系列化來深拷貝一個對象。
    通常做法,如下:介面ICloneable唯一成員是object Clone(),實現該介面只是為了表明該類型的實現可以被拷貝

    [Serializable]
    class Person : ICloneable
    {
    public string ID {get;set;}
    public int Age {get;set;}
    public Work work {get;set;} 
    //實現ICloneable介面的Clone()
    public object Clone()
    {
        return this.MemberwiseClone();
    }
    //自定義深拷貝方法
    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;
        }
     }
    }

17.集合的遍歷

  • for迴圈:採用索引器,for迴圈的優點是遍歷過程中可以修改集合的元素。
  • foreach迴圈:採用迭代器,遍歷過程中無法對集合增刪元素操作,因為迭代器只對原始版本的集合進行遍歷,每次迭代都會進行版本判斷,若集合發生變化,將拋出異常。- - - - foreach迴圈的優點是語法更簡潔,且迭代完畢後自動調用Dispose()(foreach迴圈內部使用了try...finally)

18.選擇正確的集合:詳解請參見《C#高級編程》,書中對集合講的很細

  • 線性:集合的每個元素都是是1對1的,大部分常用集合都是線性集合
  • 非線性:1對多、多對1、多對多(樹、集HashSet、圖)
  • 直接存取:具有索引器,元素按索引器排列,訪問、查找速度快,在末尾添加刪除速度也快,但在中間刪除、插入元素效率低(需要移動後面的所有元素)。(數組、List、字元串、結構)
  • 順序存取:即線性表,可動態擴大或縮小,通過對地址的引用來搜索元素,刪除、插入元素效率高,但查找效率低(需要遍歷查找)(Stack、Queue、Dictionary、LinkedList等)
  • 多線程集合類:位於System.Collections.Concurrent命名空間中,如ConcurrentBag對應於List、ConcurrentDictionary、ConcurrentStack、ConcurrentQueue

實現自定義集合類時,不要繼承自內置的集合類,而應該自行實現相應的泛型介面:
IEnumerable:提供迭代功能
ICollection:提供常用操作
IList

19.泛型

  • 避免為自定義泛型定義靜態成員,在不同的類型之間共用靜態成員沒意義。
  • 記得為泛型參數設定必要的約束,因為約束之後可以使泛型參數成為一個實實在在的"對象",可以訪問到約束類型的實例成員,而不做約束的話僅僅是一個object對象
  • 必要時用default(T)為泛型類型變數指定預設值,如T param = default(T);

20.委托

預定義的委托類型能滿足大部分日常需求,我們沒有必要聲明自己的委托類型。

  • Action,Action:接受0個或多個輸入參數,無返回值
  • Func,Func:接受0個或多個輸入參數,帶返回值,類型是TResult
  • Predicate:表示定義一組條件並判斷參數是否符合條件
    具有特定用途的委托:
  • 事件委托:

    public delegate void EventHandler(object sender, EventArgs e);
    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
  • 線程中的委托:

    public delegate void ThreadStart(); //無參數
    public delegate void ParameterrizedThreadStart(object obj); //參數對象obj
  • 非同步回調委托:
    public delegate void AsyncCallback(IAsyncResult ar);

21.對於只用一次,且主體語句數量較少的方法,應該使用Lambda表達式,它通常用於註冊給委托、或作為其它方法的參數(參數類型是匹配的委托類型)

22.理解委托的本質:

  • 委托是一個類
  • 委托保存著對註冊方法的引用(方法指針),多播委托保存著一組方法指針
  • 執行委托,將按順序調用方法指針指向的方法
  • 對一個委托實例用"="賦值一個新的方法指針時,將會調用構造函數實例化一個新的委托對象
  • 所以在實例化一個委托對象之後後,應該時刻記住使用"+="、"-="來增加、刪除新的方法指針
  • 委托類的方法:Invoke()預設調用、線上程池中啟用一個新線程調用BeginInvoke()、停止EndInvoke()

23.事件也是委托,加了event關鍵字是為了限制委托:

  • 禁止了在包含類外部對委托事件對象使用"="賦值,確保不會被覆蓋或賦值為null
  • 禁止了在包含類外部對委托事件對象的直接調用,事件的調用應該是包含類的責任
  • 參數1是觸發者對象的引用,參數2是EventArgs或其派生類的對象(可包含一些將在事件觸發時需要用到的數據)

24.當委托和Lambda小心閉包對象

(特別是在迴圈體中的迴圈變數,對於C#5.0的foreach則不必擔心)

  • 當Lambda表達式引用了局部變數時,編譯器就會自動創建一個閉包對象(如TempClass),該對象的成員包含一個對局部變數的引用(如TempClass.i)、和一個與Lambda表達式等價的方法(如TempClass.add,該方法持有對局部變數的引用)。
  • 而該閉包對象中的方法成員TempClass.add最終被賦給了委托(如MyDel),而委托通常在局部變數的作用域之外才執行。
    也就是說,委托中註冊的方法持有了對局部變數的引用,形成了像JavaScript中的閉包一樣的效果,執行委托方法時,局部變數的值將是最新值,而不是給委托註冊方法時的局部變數值。

    public static void Main()
    {
        Action act=new Action(()=>Console.WriteLine("Begin"));
        for (int i = 0; i < 5; i++)
        {
            act += () => Console.WriteLine(i.ToString());
        }
        act(); //Begin 5 5 5 5 5  因為委托方法持有了對i的引用,當前i的值為5
        Console.ReadKey();
    }
    public static void Main()
    {
        Action act=new Action(()=>Console.WriteLine("Begin"));
        for (int i = 0; i < 5; i++)
        {
            int temp = i; //每次都用一個新的temp變數來保存當前的i值
            act += () => Console.WriteLine(temp.ToString());
        }
        act(); //Begin 0 1 2 3 4
        Console.ReadKey();
    }

25.賦值為null,大部分情況下不能提前垃圾回收。

  • 沒有必要將沒用的實例成員顯式賦值為null,因為編譯器會忽略該語句。
  • 只有對日後確實沒用的靜態欄位顯式賦值為null才有必要,但要確保不會再用到它(或者說不會再用到它的包含類)。
  • 把一個對象賦值為null,它的靜態成員不會跟著變為null,因為靜態成員跟類的實例無關,它會一直留在記憶體中,除非顯式賦值為null。

後續還有很多其它方面的,如系列化與反系列化,異常處理等,由於篇幅有限,只能等下一篇再發佈了




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本節內容 1.github介紹 很多人都知道,Linus在1991年創建了開源的Linux,從此,Linux系統不斷發展,已經成為最大的伺服器系統軟體了。 Linus雖然創建了Linux,但Linux的壯大是靠全世界熱心的志願者參與的,這麼多人在世界各地為Linux編寫代碼,那Linux的代碼是如何 ...
  • 觀察系統當前進程的運行情況的命令是( ):A、freeB、dmesgC、topD、last 答案:http://hovertree.com/tiku/bjag/foxg5n0q.htm Linux系統通過( )命令給其他用戶發消息?A.lessB.mesg yC.writeD.echo to 答案: ...
  • nginx工作模式-->1個master+n個worker進程 安裝nginx的所需pcre庫【用於支持rewrite模塊】 下載軟體方法: 搜索 pcre download 網址:http://pcre.org 下載pcre包 wget ftp://ftp.csx.cam.ac.uk/pub/so ...
  • 1、在/etc/init.d/目錄下創建 nginx 文件,內容如下: 2.設置/etc/init.d/nginx 執行許可權 3.設置開機預設啟動 5.nginx控制命令 ...
  • 曾經六六我也是一個初級開發的實習生,當年的我初入農田,一心要做一個高產量高品質的碼農,做碼農界的袁隆平!然而,現實總是無比的殘酷,我一個剛入門的碼農,剛進農田就跌了許多跟頭。BUG天天困擾著我,不過這也是一種磨練。不然我六六也難以成為一個合格的碼農! 不過現在時代不同了,軟體公司越來越多,軟體開發行 ...
  • 委托這個東西不是很好理解,可是工作中又經常用到,你隨處可以看到它的身影,真讓人有一種又愛又恨的感覺,我相信許多人被它所困擾過。 一提到委托,如果你學過C語言,你一定會馬上聯想到函數指針。 什麼是委托?委托是C#中類型安全的,可以訂閱一個或多個具有相同簽名方法的函數指針。委托可以把函數做為參數傳遞,其 ...
  • 使用NHibernate實現一對多,多對一的關聯很是簡單,可如果要用複合主鍵實現確實讓人有些淡淡的疼。雖然很淡疼但還是要去抹平這個坑,在下不才,願意嘗試。 以示例進入正文,源碼下載地址: 一、數據表關係圖 很明顯,他是一個自引用數表,實現無限級樹結構的存儲。 二、關鍵步驟 註解如何實現複合主鍵 根據 ...
  • GMap學習筆記 1、GMap體系詳解 What is the map control (GMapControl)? This is the control which renders the map. What is an Overlay (GMapOverlay)? This is a laye ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...