C#程式編寫高質量代碼改善的157個建議【16-19】[動態數組、迴圈遍歷、對象集合初始化]

来源:https://www.cnblogs.com/ljdong7/archive/2019/12/10/12014499.html
-Advertisement-
Play Games

前言 軟體開發過程中,不可避免會用到集合,C#中的集合表現為數組和若幹集合類。不管是數組還是集合類,它們都有各自的優缺點。如何使用好集合是我們在開發過程中必須掌握的技巧。不要小看這些技巧,一旦在開發中使用了錯誤的集合或針對集合的方法,應用程式將會背離你的預想而運行。 本文已更新至http://www ...


前言

    軟體開發過程中,不可避免會用到集合,C#中的集合表現為數組和若幹集合類。不管是數組還是集合類,它們都有各自的優缺點。如何使用好集合是我們在開發過程中必須掌握的技巧。不要小看這些技巧,一旦在開發中使用了錯誤的集合或針對集合的方法,應用程式將會背離你的預想而運行。

  本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄以下內容:

  建議16、元素數量可變的情況下不應使用數組

  建議17、在多數情況下使用foreach進行迴圈遍歷

  建議18、foreach不能代替for

  建議19、使用更有效的對象和集合初始化

建議16、元素數量可變的情況下不應使用數組

  在C#中,數組一旦被創建,長度就不能改變。如果我們需要一個動態且可變長度的集合,就應該使用ArrayList或List<T>來創建。而數組本身,尤其是一維數組,在遇到要求高效率的演算法時,則會專門被優化以提升其效率。一維數組也成為向量,其性能是最佳的,在IL中使用了專門的指令來處理它們。

  從記憶體使用的角度來講,數組具有以下特點:

  1、數組在創建時被分配了一段固定長度的記憶體。

  2、如果數組元素是值類型,則每個元素的長度等於相應的值類型的長度

  3、如果數組的元素是引用類型,則每個元素的長度為該引用類型的IntPtr.Size。

  4、數組的存儲結構一旦被分配,就不能再變化。

  而ArryaList是這樣的:

  1、ArrayList是鏈表結構,可以動態增減記憶體空間。

  2、如果ArrayList存儲的是值類型,則會為每個元素增加12位元組的空間,其中4位元組用於對象引用,8位元組是元素裝箱時引入的對象頭。

  而List<T>是ArrayList的泛型實現,它省去了拆箱和裝箱帶來的開銷。

如果一定要動態改變數組的長度,一種方法是將數組轉換為ArrayList或List<T>,如下麵的代碼所示:

複製代碼
            ///定義一個一維數組
            int[] iArr = { 0,1,3,4,6,7,9};
            ///將數組轉換為ArrayList
            ArrayList arrayListInt = ArrayList.Adapter(iArr);
            arrayListInt.Add(11);
            ///將數組轉換為List<T>
            List<int> listInt = iArr.ToList<int>();
            listInt.Add(11);
複製代碼

  還有一種方法是用數組的複製功能。數組繼承自System.Array,抽象類System.Array提供了一些有用的實現方法,其中就包含了Copy方法,它負責將一個數組的內容複製到另外一個數組中。無論是哪種方法,改變數組長度就相當於重新創建了一個數組對象。

  為了讓數組看上去本身就具有動態改變長度的功能,還可以創建一個名為ReSize的擴展方法。

複製代碼
    public static class ClassForExtensions
    {
        public static Array ReSize(this Array array,int newSize)
        {
            Type t = array.GetType().GetElementType();
            Array newArray = Array.CreateInstance(t, newSize);
            Array.Copy(array, 0, newArray, 0, Math.Min(array.Length, newSize));
            return newArray;
        }
    }
複製代碼

調用方式如下:

        static void Main(string[] args)
        {
            int[] iArr = { 0,1,3,4,6,7,9};
            iArr = (int[])ClassForExtensions.ReSize(iArr, 20);
            Console.ReadLine();
        }

下麵我們來對比一下性能,先來看代碼:

複製代碼
    class Program
    {
        static void Main(string[] args)
        {
            ResizeArray();
            ResizeList();
            Console.ReadLine();
        }

        public static void ResizeArray()
        {
            int[] iArr = {0,1,3,4,6,8 };
            Stopwatch watch = new Stopwatch();
            watch.Start();///用於測量時間間隔
            iArr = (int[])iArr.ReSize(10);
            watch.Stop();///
            Console.WriteLine("ResizeArray:{0}", watch.Elapsed);
        }

        public static void ResizeList()
        {
            List<int> iArr = new List<int>(new int[] { 0, 1, 3, 4, 6, 8, 9 });
            Stopwatch watch = new Stopwatch();
            watch.Start();
            iArr.Add(0);
            iArr.Add(0);
            iArr.Add(0);
            watch.Stop();
            Console.WriteLine("ResizeList:{0}", watch.Elapsed);
        }
    }
複製代碼

Main函數中主要是調用,自己定義的兩個方法,第一個是重新設置數組的長度,第二個是設置List<T>的長度,通過運行時間進行測量:

嚴格意義上講,List<T>不存在改變長度的說法,此處主要是來進行對比一下,對List<T>設置長度,並且進行賦值,即便是這樣,在時間效率上ResizeList比ResizeArray要高很多很多。

建議17、在多數情況下使用foreach進行迴圈遍歷

 這裡關於如何針對集合才能使用foreach進行遍歷我剛剛寫了一篇有關IEnumerable和IEnumerator兩個介面的文章,有興趣的話可以看一下。http://www.cnblogs.com/aehyok/p/3641193.html

感覺使用foreach進行迴圈遍歷,總共有三個好處吧:

1、提供了比較簡單、簡潔的語法。

2、自動將代碼置入try-finally塊

3、若類型實現IDispose介面,foreach會在迴圈結束後自動調用Dispose方法

建議18、foreach不能代替for

foreach存在一個問題是:它不支持迴圈時對集合進行增刪操作。我們來看一下簡單的例子:

複製代碼
            List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
            foreach (int item in list)
            {
                list.Remove(item);
                Console.WriteLine(item.ToString());
            }
            Console.ReadLine();
複製代碼

一起看一下執行結果:

那麼下麵我們來使用for進行嘗試:

            List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
            for (int i = 0; i < list.Count(); i++)
            {
                list.Remove(list[i]);
            }
            Console.ReadLine();

  進行刪除肯定是沒問題的。但是要仔細看一下,比如它第一次刪除索引0的時候,也就是刪除了1,那麼它會立即重新調整索引,然後第二次刪除的時候,刪除的不是2,而是3這個項。那麼最終運行完發現還剩餘兩項

 foreach迴圈使用了迭代器進行集合的遍歷,它在FCL提供的迭代器內部維護了一個對集合版本的控制。那麼什麼是集合版本呢?簡單的說,其實它就是一個整型的變數,任何對集合的增刪操作都會使版本號加1。foreach迴圈會調用MoveNext方法來遍歷元素,在MoveNext方法內部會進行版本號的檢測,一旦檢測到版本號有變動,就會拋出InvalidOperationException異常。

  如果使用for迴圈就不會帶來這樣的問題。for直接使用所引器,它不對集合版本號進行判斷,所以不存在因為集合的變動而帶來的異常(當然,超出索引長度這種情況除外)。

  索引,因為版本檢測的緣故,foreach迴圈並不能帶起for迴圈。 

建議19、使用更有效的對象和集合初始化

   對象初始化設定項支持可以直接在大括弧中對自動實現的屬性進行賦值。

複製代碼
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "aehyok", Age = 25 };
            Console.ReadLine();
        }
    }
複製代碼

以往只能依靠構造方法傳值進去,或者在對象構造完畢後對屬性進行賦值。現在這些步驟簡化了,初始化設定項實際相當於編譯器在對象生成後對屬性進行了賦值。

複製代碼
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "Kris", Age = 22 };
            List<Person> personList = new List<Person>()
            { 
                new Person() { Name = "aehyok", Age = 25 },
                person,
                null
            };
            Console.ReadLine();
        }
    }
複製代碼

使用集合的初始化設定項,編譯器會在集合對象創建完畢後對集合調用Add方法。上面這段代碼展示瞭如何在初始化語句中創建一個新對象或一個現有對象,以及一個null值。

 不過,初始化設定項絕不僅僅是為了對象和集合初始化的方便,它更重要的作用是為LINQ查詢中的匿名類型進行屬性的初始化。由於LINQ查詢返回的集合中匿名類型的屬性都是只讀的,如果需要為匿名類型屬性賦值,或者增加屬性,只能通過初始化設定項來進行。初始化設定項還能為屬性使用表達式。

來看一段代碼:

複製代碼
List<Person> lst = new List<Person>()
 { 
    new Person(){ Age = 10,Name="Tommy"},
    new Person(){ Age = 20,Name="Sammy"}
 };
var entity = from p in lst
             select new { p.Name, AgeScope = p.Age > 10 ? "Old" : "Young" };
foreach (var item in entity)
{
   Response.Write(string.Format("name is {0},{1}", item.Name, item.AgeScope));
}
複製代碼

AgeScope 屬性是經過計算得出的,有瞭如此方便的初始化方式,使得代碼更加優雅靈活。


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

-Advertisement-
Play Games
更多相關文章
  • Netnr.FileServer 基於 .NET Core 的簡單文件伺服器,資料庫為SQLite 源碼 https://github.com/netnr/blog https://gitee.com/netnr/blog 為了方便維護公共類庫,項目放置一個解決方案,沒有分開 配置 為配置文件 正式 ...
  • 這是 站點的源代碼 源碼 框架組件 jQuery + Bootstrap4 .NET Core (latest) EF + Linq 支持:SQLServer、MySQL、PostgreSQL、SQLite、InMemory等 ==================================== ...
  • 此系列使用Asp.net構建前後端分離的博客網站。 創建一個asp.net項目 我們這裡使用的是空模板,把Https配置去掉(安全先不配置) 構建webapi介面有很多方法,在這裡我們選擇最簡單的2種方式進行搭建。 1.WebForm 創建一個webForm 打開窗體的伺服器邏輯代碼文件 添加如下方 ...
  • 1. DateTime的使用 DateTime dt = DateTime.Now; StringBuilder sb = new StringBuilder(); sb.AppendLine(string.Format("DataTime字元串表示: {0}", dt.ToString())); ...
  • Centos+Nginx+NetCore3.1部署 1 先將vs2019升級到,16.4.0版本 2.新建一.netcore3.1的web程式 3.編譯後將項目上傳到centos伺服器 4.配置nginx文件 修改完後重新載入ngxin. service nginx restart 5.安裝守護進程 ...
  • 5年,50+版本迭代,不忘初心,始終為了幫助廣大站長更容易實現站內搜索功能 一,開發初衷與思路: 基於Lucene.net重頭開發一個站內搜索其實挺費時,且需要一定的二開能力,很多人直接組合一些第三方開源的組件,但如果需要解決好搜索質量,兼顧搜準率與召回率,依然需要做不少調整優化的工作,後來我想能否 ...
  • 常見面試題目: 1. 值類型和引用類型的區別? 2. 結構和類的區別? 3. delegate是引用類型還是值類型?enum、int[]和string呢? 4. 堆和棧的區別? 5. 什麼情況下會在堆(棧)上分配數據?它們有性能上的區別嗎? 6.“結構”對象可能分配在堆上嗎?什麼情況下會發生,有什麼 ...
  • 建議20、使用泛型集合來替代非泛型集合 http://www.cnblogs.com/aehyok/p/3384637.html 這裡有一篇文章,是我之前專門來介紹泛型的。我們應儘量的使用泛型集合。因為泛型的確有它的好處: 1、提供了類型安全,在編譯期間就可以檢查錯誤 2、更重要的是大部分情況下泛型 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...