編寫高質量代碼改善C#程式的157個建議[正確操作字元串、使用預設轉型方法、卻別對待強制轉換與as和is]

来源:http://www.cnblogs.com/shouce/archive/2016/04/08/5366445.html
-Advertisement-
Play Games

前言 本文主要來學習記錄前三個建議。 建議1、正確操作字元串 建議2、使用預設轉型方法 建議3、區別對待強制轉換與as和is 其中有很多需要理解的東西,有些地方可能理解的不太到位,還望指正。 建議1、正確操作字元串 字元串應該是所有編程語言中使用最頻繁的一種基礎數據類型。如果使用不慎,我們就會為一次 ...


前言

  本文主要來學習記錄前三個建議。

  建議1、正確操作字元串

  建議2、使用預設轉型方法

  建議3、區別對待強制轉換與as和is

其中有很多需要理解的東西,有些地方可能理解的不太到位,還望指正。

建議1、正確操作字元串

  字元串應該是所有編程語言中使用最頻繁的一種基礎數據類型。如果使用不慎,我們就會為一次字元串的操作所帶來的額外性能開銷而付出代價。本條建議將從兩個方面來探討如何規避這類性能開銷:

  1、確保儘量少的裝箱

  2、避免分配額外的記憶體空間

先來介紹第一個方面,請看下麵的兩行代碼:

String str1="str1"+9;
String str2="str2"+9.ToString();

從IL代碼可以得知,第一行代碼在運行時完成一次裝箱的行為,而第二行代碼中並沒有發生裝箱的行為,它實際調用的是整型的ToString()方法,效率要比裝箱高。所以,在使用其他值引用類型到字元串的轉換並完成拼接時,應當避免使用操作符“+”來完成,而應該使用值引用類型提供的ToString()方法。

第二方面,避免分配額外的記憶體空間。對CLR來說,string對象(字元串對象)是個很特殊的對象,它一旦被賦值就不可改變。在運行時調用System.String類中的任何方法或進行任何運算(如“=”賦值、“+”拼接等),都會在記憶體中創建一個新的字元串對象,這也意味著要為該新對象分配新的記憶體空間。像下麵的代碼就會帶來運行時的額外開銷。

複製代碼
private static void NewMethod1()
{
      string s1="abc";
      s1="123"+s1+"456";    ////以上兩行代碼創建了3個字元串對象對象,並執行了一次string.Contact方法            
}

private static void NewMethod2()
{
      string re=9+"456";    ////該方法發生了一次裝箱,並調用一次string.Contact方法  
}
複製代碼

關於裝箱拆箱的問題大家可以查看我之前的文章http://www.cnblogs.com/aehyok/p/3504449.html

而以下代碼,字元串不會在運行時進行拼接,而是會在編譯時直接生成一個字元串。

複製代碼
private static void NewMethod3()
{
       string re2="123"+"abc"+"456";   ///該代碼等效於///string re2="123abc456";  
}

private static void NewMethod4()
{
      const string a="t";
      string re="abc"+a;   ///因為a是一個常量,所以該代碼等效於string=re="abc"+"t";  最終等效於string re="abct";  
}  
複製代碼

由於使用System.String類會在某些場合帶來明顯的性能損耗,所以微軟另外提供了一個類型StringBuilder來彌補String的不足。

StringBuilder並不會重新創建一個string對象,它的效率源於預先以非托管的方式分配記憶體。如果StringBuilder沒有先定義長度,則預設分配的長度為16。當StringBuilder字元串長度小於等於16時,StringBuilder不會重新分配記憶體;當StringBuilder字元長度大於16小於32時,StringBuilder又會重新分配記憶體,使之成為16的倍數。在上面的代碼中,如果預先判斷字元串的長度將大於16,則可以為其設定一個更加合適的長度(如32)。StringBuilder重新分配記憶體時是按照上次容量加倍進行分配的。當然,我們需要註意,StringBuilder指定的長度要合適,太小了,需要頻繁分配記憶體,太大了,浪費空間。

查看以下代碼,比較下麵兩種字元串拼接方式,哪種效率更高:

複製代碼
        private static void NewMethod1()
        {
            string a = "t";
            a += "e";
            a += "s";
            a += "t";
        }

        private static void NewMethod2()
        {
            string a = "t";
            string b = "e";
            string c = "s";
            string d = "t";
            string result = a + b + c + d;
        }
複製代碼

  結果可以得知:兩者的效率都不高。不要以為前者比後者創建的字元串對象更少,事實上,兩者創建的字元串對象相等,且前者進行了3次string.Contact方法調用,比後者還多了兩次。

  要完成這樣的運行時字元串拼接(註意:是運行時),更佳的做法是使用StringBuilder類型,代碼如下所示:

複製代碼
        public static void NewMethod()
        {
            ////定義了四個變數
            string a = "t";
            string b = "e";
            string c = "s";
            string d = "t";
            StringBuilder sb = new StringBuilder(a);
            sb.Append(b);
            sb.Append(c);
            sb.Append(d);
            
            ///提示是運行時,所以沒有使用以下代碼
            //StringBuilder sb = new StringBuilder("t");
            //sb.Append("e");
            //sb.Append("s");
            //sb.Append("t");
            //string result = sb.ToString();
        }
複製代碼

微軟還提供了另外一個方法來簡化這種操作,即使用string.Format方法。string.Format方法在內部使用StringBuilder進行字元串的格式化,代碼如下所示:

複製代碼
        public static void NewMethod4()
        {
            string a = "t";
            string b = "e";
            string c = "s";
            string d = "t";
            string result = string.Format("{0}{1}{2}{3}", a, b, c, d);
        }
複製代碼

 對於String和StringBuilder的簡單介紹也可以參考我之前的一篇文章http://www.cnblogs.com/aehyok/p/3505000.html

建議2、使用預設轉型方法

1、使用類型的轉換運算符,其實就是使用類型內部的一方方法(即函數)。轉換運算符分為兩類:隱式轉換和顯式轉換(強制轉換)。基元類型普遍都提供了轉換運算符。

所謂“基元類型”,是指編譯器直接支持的數據類型。基元類型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。

            int i = 0;
            float j = 0;
            j = i;  ///int 到float存在一個隱式轉換
            i = (int)j; ///float到int必須存在一個顯式轉換

用戶自定義的類型也可以通過重載轉換運算符的方式提供這一類轉換:

複製代碼
    public class Ip
    {
        IPAddress value;
        public Ip(string ip)
        {
            value = IPAddress.Parse(ip);
        }

        //重載轉換運算符,implicit 關鍵字用於聲明隱式的用戶定義類型轉換運算符。
        public static implicit operator Ip(string ip)
        {
            Ip iptemp = new Ip(ip);
            return iptemp;
        }

        //重寫ToString方法
        public override string ToString()
        {
            return value.ToString();
        }
    }
    class Program
    {
        public static void Main(string[] args)
        {
            Ip ip = "192.168.1.1";   //通過Ip類的重載轉換運算符,實現字元串到Ip類型的隱式轉換
            Console.WriteLine(ip.ToString());
            Console.ReadLine();
        }
    }
複製代碼

提供的就是字元串到類型Ip之間的隱式轉換。

2、使用類型內置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法

比如從string轉換為int,因為其經常發生,所以int本身就提供了Parse和TryParse方法。一般情況下,如果要對某類型進行轉換操作,建議先查閱該類型的API文檔。

3、使用幫助類提供的方法

可以使用System.Convert類、System.BitConverter類來進行類型的轉換。

System.Convert提供了將一個基元類型轉換為其他基元類型的方法,如ToChar、ToBoolean方法等。值得註意的是,System.Convert還支持將任何自定義類型轉換為任何基元類型,只要自定義類型繼承了IConvertible介面就可以。如上文中的IP類,如果將Ip轉換為string,除了重寫Object的ToString方法外,還可以實現IConvertible的ToString()方法

 

 繼承IConvertible介面必須同時實現其他轉型方法,如上文的ToBoolean、ToByte,如果不支持此類轉型,則應該拋出一個InvalidCastException,而不是一個NotImplementedException。

4、使用CLR支持的轉型

CLR支持的轉型,即上溯轉型和下溯轉型。這個概念首先是在Java中提出來的,實際上就是基類和子類之間的相互轉換。

就比如: 動作Animal類、Dog類繼承Animal類、Cat類也繼承自Amimal類。在進行子類向基類轉型的時候支持隱式轉換,如Dog顯然就是一個Animal;而當Animal轉型為Dog的時候,必須是顯式轉換,因為Animal還可能是一個Cat。

            Animal animal = new Animal();
            Dog dog = new Dog();
            animal = dog;   /////隱式轉換,因為Dog就是Animal
            ///dog=animal;  ////編譯不通過
            dog = (dog)animal; /////必須存在一個顯式轉換

 

建議3、區別對待強制轉換與as和is

首先來看一個簡單的實例

            FirstType firstType = new FirstType();
            SecondType secondType = new SecondType();
            secondType = (SecondType)firstType;

從上面的三行代碼可以看出,類似上面的應該就是強制轉換。

首先需要明確強制轉換可能意味這兩件不同的事情:

1、FirstType和SecondType彼此依靠轉換操作來完成兩個類型之間的轉換。

2、FirstType是SecondType的基類。

類型之間如果存在強制轉換,那麼它們之間的關係要麼是第一種,要麼是第二種。不可能同時是繼承的關係,又提供了轉型符。

針對第一種情況:

複製代碼
    public class FirstType
    {
        public string Name { get; set; }
    }

    public class SecondType
    {
        public string Name { get; set; }

        public static explicit operator SecondType(FirstType firstType)
        {
            SecondType secondType = new SecondType() { Name = "轉型自:" + firstType.Name };
            return secondType;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FirstType firstType = new FirstType() { Name="First Type"};
            SecondType secondType = (SecondType)firstType;  ///此轉換是成功的
            secondType = firstType as SecondType;           ///編譯不通過
            Console.ReadLine();
        }
    }
複製代碼

這裡上面也有添加註釋,通過強制轉換是可以轉換成功的,但是使用as運算符是不成功的編譯就不通過。

這裡就是通過轉換符進行處理的結果。

接下來我們再在Program類中添加一個方法

複製代碼
        static void DoWithSomeType(object obj)
        {
            ///編譯器首先判斷的是,SeondType和ojbect之間有沒有繼承關係。
            ///因為在C#中,所有的類型都是繼承自object的,所以這裡編譯沒有什麼問題。
            ///但編譯器會自動產生代碼來檢查obj在運行時是不是SecondType,這樣就繞過了操作轉換符,導致轉換失敗。
            SecondType secondType = (SecondType)obj;
        }
複製代碼

如註釋所說的,編譯通過執行報錯的問題。

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

現在可以如上方法改寫為

        static void DoWithSomeType(object obj)
        {
            SecondType secondType = obj as SecondType;
        }

保證編譯執行都不會報錯。as操作符永遠不會拋出異常,如果類型不匹配(被轉換對象的運行時類型既不是所轉換的目標類型,也不是其派生類型),或者轉型的源對象為null,那麼轉型之後的值也為null。改造前的DoWithSomeType方法會因為引發異常帶來效率問題,而使用as後,就可以完美的避免這種問題。

 

現在來看第二種情況,即FirstType是SecondType的基類。這種情況下,既可以使用強制轉型又可以使用as操作符。

複製代碼
    public class FirstType 
    {
        public string Name { get; set; }
    }

    public class SecondType : FirstType
    {
    }
    class Program
    {
        static void Main(string[] args)
        {
            SecondType secondType = new SecondType() { Name="aehyok"};
            FirstType firstType = (FirstType)secondType;
            firstType = secondType as FirstType;
            Console.ReadLine();
        }
    }
複製代碼

但是,即使可以使用強制轉型,從效率的角度來看,也建議大家使用as進行轉型。

下麵再來看一下is操作符

複製代碼
        static void DoWithSomeType(object obj)
        {
            if (obj is SecondType)
            {
                SecondType secondType = obj as SecondType;
            }
        }
複製代碼

這個版本的效率顯然沒有上一個版本的效率高。因為當前這個版本進行了兩次類型檢測。但是,as操作符有個問題,就是它不能操作基元類型。如果涉及到基元類型的演算法,那麼就要使用is進行判斷之後再進行轉型的操作,以避免轉型失敗。

 


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

-Advertisement-
Play Games
更多相關文章
  • 1. 軟體源 2. 更新並升級已有軟體,並安裝一些基礎依賴庫 3. 安裝軟體 Nginx/MySQL/Redis/Git/SVN apt-get install nginx mysql-server redis-server git subversion PHP 及核心擴展 apt-get inst ...
  • 這個問題發生後,我覺得很疑惑,因為伺服器上確定沒有安裝印表機。那麼印表機是從哪裡來的呢? 通過百度搜索,發現網上的一個帖子解答了我的疑惑。原帖地址:http://blog.chinaunix.net/uid-563697-id-1989152.html 原來是連接遠程桌面時,將本地的印表機共用的原因 ...
  • 安裝了一個ubuntu 15.10,沒有集成vim,很失望,先安裝個vim,sudo apt-get install vim。 開始獲取g++-5: $ sudo add-apt-repository ppa:ubuntu-toolchain-r/test $ sudo apt-get update ...
  • VMware下,centos系統,安裝好後,一直連不上外網。在網上查了好多資料,都沒有找到解決的辦法。最後,把自己的解決方案,寫到下麵。 第一步、打開文件 $ vim /etc/sysconfig/network-scripts/ifcfg-eth0 #自動獲取DEVICE=eth0BOOTPROT ...
  • 安裝php yum install php yum install php-mysql php-gd php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc yum install php yum install php-mysql php-gd ...
  • 作為本系列博文的開篇,有必要先做些聲明,用於免責、以絕口水: 1. 博文僅圍繞已經棄用的、C/S結構的《上海市個人非營業性客車額度競拍程式》客戶端(NetBidClient)進行介紹,對於正在使用的系統不進行任何討論。 2. 作者從未向“代拍黃牛”提供過任何技術支持或外掛軟體,也沒有依賴相關技術從事 ...
  • 有一段時間沒有接著微信的主題繼續介紹裡面的功能模塊了,這段時間來,微信也做了不少的變化改動,針對這些特性我全面核對了一下相關的微信公眾號和企業號的介面,對原有的微信API和系統管理做了全面的更新,本隨筆以及後面的隨筆就是基於這個工作上的總結,以期把微信涉及的功能模塊,都使用C#實現的方式來介紹。本隨 ...
  • 研究ing 待續。。。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...