深入理解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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...