[C#] Large Object Heap

LOH(Large Object Heap) GC .NET 具有 GC(garbage collector),使開發員不需要過多考慮記憶體控管,因為 GC 會自動移除「死掉的」物件,並將記憶體重排。 這些小的物件會透過釋放死的物件,並移動集合中存活的物件,以確保沒有間隙。(事實上並非原地移動,而是將之全部複製到新的記憶體區塊,這樣可以簡化分配記憶體的過程,這樣意味著,空間都會出現在區塊的尾端,所以不需要進行掃描來找尋記憶體區塊來儲存新的物件。 LOH LOH(Large Object Heap) 指的是大型物件堆,也就是大小超過 85000 bytes 的物件,GC 會將之視為獨立的部分,GC 會優先處理其它的堆,原因是若要透過複製移動的方法來重新分配 LOH,它們需要兩倍的記憶體來進行,會使得 GC 花費大量的時間進行任務。 取而代之,GC 不會移動 LOH,而是將之留在原地,使得空間變得碎片化(fragmented)。當移除 LOH 時,會將空間原地保留,當要放入新的物件時,若區塊的尾端沒有足夠的空間,則會在這些 LOH 之間的空洞找尋可用空間,在沒有足夠空間時,擴展堆。 隨著時間堆移,即使不會發生 memory leakage,由於 LOH 之間的碎片空間會愈來愈小,不足以放置新的物件。在最糟的狀況下,這些碎片空間佔有的空間很大,但又不足以放置新的物件,且碎片的個數很多,這可能就會導致更多的產生 OutOfMemoryException。 如何解決 防止或減少使用大型物件的需求。 定期停止或重啟受影響的應用程式。 將大型物件重構成不同的數據結構,如 100000 個元素的數組,儲存成 10 個 10000 個元素的數組。 System.Collections 中也有一個 Capacity 的屬性來促進這種設計模式來避免大型物件的產生,有效的共享大型數據結構也有助於減少大型物件。 Reference: Andrew Hunter - The Dangers of the Large Object Heap Reference: Microsoft - The large object heap on Windows systems ...

<span title='2023-07-22 20:59:57 +0800 +0800'>July 22, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[CS] Sample cost for performance test

Sample code for stop watch using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Runtime.CompilerServices; using System.Data.Common; using System.Diagnostics; using System.Drawing; using System.Text; using System.Threading.Tasks; namespace Rainspace.PlayGround { public class Program { public static void Main(string[] args) { Stopwatch sw = new Stopwatch(); sw.Start(); System.Console.WriteLine("==============Accumulate1=============="); System.Console.WriteLine("Result: " + Accumulate(0, 100, 0)); sw.Stop(); System.Console.WriteLine("Time cost: " + (sw.ElapsedTicks/1.0e6).ToString() + "ms"); sw.Reset(); sw.Start(); System.Console.WriteLine("==============Accumulate2=============="); System.Console.WriteLine("Result: " + Accumulate(0, 100, 0)); sw.Stop(); System.Console.WriteLine("Time cost: " + (sw.ElapsedTicks/1.0e6).ToString() + "ms"); } public static int Accumulate(int begin, int end, int sum = 0) { int res = sum; if (end < begin) return Accumulate(end, begin, sum); for (int i = begin; i < end; i++) { res += i; } return res; } public static int Accumulate2(int begin, int end, int sum = 0) { int res = (begin + end) * (end - begin) / 2; return res + sum; } } }

<span title='2023-05-16 21:26:24 +0800 +0800'>May 16, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] C#3、LINQ 及相關特性

1. 屬性的自動實現 在 C#3 以前,每個屬性需要手動實現,也就是需要手動為屬性添加 get 訪問器與 set 訪問器,如: private string name; public string Name { get { return name; } set { name = value; } } 在 C#3 以後,可以透過自動實現的方式撰寫: 其中命名為 name 的變數由編譯器自動創建並為其賦予名稱。 public string Name { get; set; } 但在 C#3 時,不能宣告 readonly 的自動屬性,且不能在宣告時賦予初始值,在 C#6 做了修正。在 C#3 只能透過訪問子分離來模擬 readonly: public string Name { get; private set; } 2. 隱式類型(var) 靜態類型 vs. 動態類型: 靜態類型是面向編譯的的語言,故所有類型由編譯器決定,在編譯期就會完成綁定。 動態類型則是延遲綁定的時間,在執行期才進行類型綁定。 顯式類型 vs. 隱式類型: 顯式類型代表在程式碼表達式便給出具體的類型訊息。 隱式類型則允許了類型推斷,透過上下文判斷出類型. C#3 開始支援隱式類型的寫法,關鍵字是 var。 var name = "Rainhu"; // string name = "Rainhu" 隱式類型的宣告有幾個限制: 變數必須在宣告時就初始化。 用於初始化的表達式必須已具備某個類型。 只能用於局部變數 var x; x = 10; // 違反1 var y = null; // 違反2 特別注意到:C# 是在編譯期就完成隱式類型的綁定 隱式類型因為其方便性,但很容易造成濫用,其適用的情形大致如下: ...

<span title='2023-05-01 14:10:48 +0800 +0800'>May 1, 2023</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;Rain Hu

[C#] Namespace Alias 命名空間別名

1. 命名空間別名 命名空間(namespace)的作用是允許在不同的命名空間下定義多個同名 class。 使用命名空間別名(namespace alias)可以避免為了保證命名唯一而使用命名空間而導致的命名過度冗長。 using System; using WinForms = System.Windows.Forms; using WebForms = System.Web.UI.WebControls; class Program { public static void Main() { Console.WriteLine(typeof(WinForms.Button)); Console.WriteLine(typeof(WebForms.Button)); } } 2. 命名空間別名限定字符 上例的程式碼存在一個潛在的問題,如果程式中同時引用了一個同名類別,如 class WinForms,那麼 WinForms.Button 就會被判斷成該類別的 Button 成員。 為了避免上面的情形發生,在 C#2 引入了命名空間別名限定字符(::),使用 :: 代表前面接的一定是命名空間。 public static void Main() { Console.WriteLine(typeof(WinForms::Button)); Console.WriteLine(typeof(WebForms::Button)); } 3. 全局命名空間別名 C#2 引入的全局命名空間別名,可以指示全局命名空間中的類別,也可以用於類別完全限定名的一個「根」命名空間。 global using global::System.DateTime; 4. 外部別名 假設有不同的程式提供了相同的命名空間,而命名空間又有相同的 class 名稱,則需要外部別名(extern)來處理。 extern alias JsonNet; extern alias JsonNetAlternative; using JsonNet::Newtonsoft.Json.Linq; using AltJObject = JsonNetAlternative::Newtonsoft.Json.Linq.JObject; JObject obj = new JObject(); AltJObject alt = new AltJObject();

<span title='2023-02-28 22:51:08 +0800 +0800'>February 28, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] Property getter/setter access separate 訪問權限分離

1. getter/setter 的訪問權限分離 在 C#1 中,getter 跟 setter 共用一個 access modifier,意思是 getter 與 setter 擁有同樣的訪問權限,這個顯然是不合理的。 private string text; public string Text { get { return text; } set { text = value; } } 更常見的情境應該是 setter 的訪問權限比 getter 的訪問權限更小,這在 C#2 中可以透過下面例子的方式來達到: private string text; public string Text { get { return text; } private set { text = value; } }

<span title='2023-02-28 22:18:25 +0800 +0800'>February 28, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] static class 靜態類別

1. 介紹 靜態類別指的是使用 static 修飾類別,可以用於裝載一個全部由靜態方法組成的工具類別。 雖然宣告一個僅包含靜態方法的非靜態類別完成是合法的,但是加註 static 關鍵字可以表明該類別的用途,並且防之該類別被當作變數類型或是類型實參使用。 public static class Util { public static string getCOL(string str, char del, int col) { string[] arr = str.Split('_'); int n = arr.Length; try { return arr[col-1]; } catch { string text = $"Index out of bound.\nIndex should be between 1 and {n}"; throw new Exception(text); } } } 另外,擴充方法(extension method) 也只能在非嵌套、非泛型類的靜態類別中宣告。

<span title='2023-02-28 21:08:01 +0800 +0800'>February 28, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] C# 筆記

C#2 Generic 泛型 Nullable 可空值類型 Delegate 委派 Iterator 迭代器 Partial 局部類型 Yield Static Class 靜態類別 Property getter/setter access separate 訪問權限分離 Namespace Alias 空間命名別名 C#3 Linq 及其相關特性

<span title='2023-02-28 18:49:39 +0800 +0800'>February 28, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] Partial Type 局部類型

1. 用法 partial 關鍵字可用於 class、interface、struct,可將之拆開於不同的檔案撰寫。 可以方便不同的工程師在不同的檔案中共同開發同一個 class、interface、struct。 public partial class Util { public static IEnumerable<int> GetFibonacci() { ... } } public partial class Util { public static IEnumerable<int> GetPrime() { ... } } 2. Partial Method 局部方法 可以在一個局部類別中只宣告方法,在另一個局部類別中可選擇實作或不實作,若不實作,編譯器會直接省略這個宣告而不編譯。 局部方法預設是 private 的 局部方法的返回值必須是 void 且不能使用 out 參數,但可以使用 ref 參數。 partial class Demo { public Demo() { OnConstruction(); // 調用未實現的局部方法 } public override string ToString() { string ret = "Original return value"; CustomizeToString(ref ret); return ret; } partial void OnConstruction(); // 宣告局部方法 partial void CustomizeToString(ref string text); // 宣告局部方法 } partial class Demo { partial void CustomizeToString(ref string text) // 實作局部方法 { text += " - Customized!"; } public static void Main(string[] args) { Demo d = new Demo(); Console.WriteLine(d.ToString()); } } 注意到上例宣告了兩個局部方法 OnStruction() 與 CustomizeToString(ref string text),但由於前者並沒有被實作,故編譯器會直接將它移除。 利用上面的這個性質,我們可以搭配使用 IDE 的 auto generation 來生成 partial 類別的部分,這樣可以便於管理與組織檔案。

<span title='2023-02-28 15:11:11 +0800 +0800'>February 28, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[C#] IEnumerable & IEnumerator 迭代器

1. 簡介 迭代器包含了以下四種類型 IEnumerable IEnumerable<T> IEnumerator IEnumerator<T> 根據迭代器的返回類型,每個迭代器都有一個 yield type。 IEnuerable 返回 object IEnuerable<string> 返回 string 基本的語法為: public static IEnumerable<int> GetFibonacciSeries(int end) { int cnt = 0; int prev = 0; int curr = 1; while (true) { if (cnt == end) yield break; cnt++; int tmp = curr + prev; yield return curr; prev = curr; curr = tmp; } } public static void Main(string[] args) { foreach(int val in GetFibonacciSeries(10)) { Console.WriteLine(val); } } 2. 延遲執行 雖然同樣的程式可以用 List<T> 的方式重新寫成下面的程式碼,其列印結果也會一樣,但在執行期卻會有很大的差異。 public static List<int> GetFibonacciSeriesList(int end) { int cnt = 0; int prev = 0; int curr = 1; List<int> res = new List<int>(); while (true) { if (cnt == end) break; cnt++; int tmp = curr + prev; res.Add(curr); prev = curr; curr = tmp; } return res; } public static void Main(string[] args) { foreach(int val in GetFibonacciSeriesList(10)) { Console.WriteLine(val); } } 延遲執行屬於 lambda 演算的一部分,目的是「只在需要獲取計算結果時執行程式」。 public static void Main(string[] args) { // 調用迭代器的方法,從 IEnumerable<T> 取得 IEnumerator<T> using (var reader = GetFibonacciSeries(10).GetEnumerator()) { while (reader.MoveNext()) { int val = reader.Current; Console.WriteLine(val); } } } 以上兩段程式碼兩段程式碼在執行期的差別為: 前者在執行到 foreach(int val in GetFibonacciSeriesList(10)) 時,會先將 GetFibonacciSeriesList(10) 執行完取得 List<int> 之後才開始 for loop。 後者在執行到 MoveNext() 時,程式碼才真正被執行。 執行 yield 語句時,程式碼會停止執行: 拋出 exception 方法執行完畢 遇到 yield break 執行到 yield return,迭代器準備返回值。 那麼延遲執行有什麼好處呢? 不必預先創建一個 List<int>,在 List 本身很大的情況下,可以節省空間。 試想今天的數列是無窮無盡的(如上例的 fibonacci 數列),使用 List<int> 就顯得太冗餘了。 使用迭代器可以達到 Design Pattern 中的關注點分離(Iteration/Process 分離),也就是說,在叫用迭代器時,只需關心有沒有辦法迭代到下一個元素。 程式只需完成需求即可退出,大大的增加效率。 試想今天需要向資料庫拿去十筆最新的資料,若不用延遲執行的話,需要將整筆資料庫的資料用某種資料結構儲存下來,再 for-loop 取出十筆。 若使用延遲執行,取得十筆資料就立即退出,可以大大減少執行時間。 3. finally 的處理 觀察下面的程式碼,並對照輸出結果,可以發現,只有出現一次的 In finally block。 只有在執行了 IEnumerator<T>.Dispose() 方法時,才會調用 finally 的區塊。 而每次的 IEnumerator<T>.MoveNext() 都會使程式停止在 yield return。 不管是用 using 搭配 IEnumerable.GetEnumerator(),或是使用 foreach(var val in Iterator()) 回傳的結果都是只有出現一次的 In finally block,代表後者隱含了一條 using 的語句。 public static IEnumerable<string> Iterator() { try { Console.WriteLine("Before first yield"); yield return "first"; Console.WriteLine("Between yields"); yield return "second"; Console.WriteLine("After yields"); } finally { Console.WriteLine("In finally block"); } } public static void Main(string[] args) { using (var reader = Iterator().GetEnumerator()) { while (reader.MoveNext()) { string val = reader.Current; Console.WriteLine(val); } } } // Before first yield // first // Between yields // second // After yields // In finally block 更多詳細的介紹詳見 [C#] Yield Return

<span title='2023-02-27 11:50:20 +0800 +0800'>February 27, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;Rain Hu

[C#] Delegate 委派

1. 委派語法 委派 (Delegate) 類似於 C/C++ 中的函式指針,是可存有對某個方法的引用的一種引用類型變數。 宣告語法:delegate <return type> <delegate-name> <parameter list> // 宣告一個委派,裝載 void function(string) public delegate void PrintString(string text); public static void Main(string[] args) { // 新增一個委派事件,指向 ToUpperCase PrintString e = new PrintString(ToUpperCase); string text = "Hello World!"; e.Invoke(text); // HELLO WORLD! // 將另一個事件加到委派中,指向 ToLowerCase e += ToLowerCase; e.Invoke(text); // HELLO WORLD! // hello world! // 將委派中的 ToUpperCase 取消掉 e -= ToUpperCase; e.Invoke(text); // hello world! } public static void ToUpperCase(string text) { Console.WriteLine(text.ToUpper()); } public static void ToLowerCase(string text) => Console.WriteLine(text.ToLower()); 2. 簡化事件註冊 假設想為一個按鍵註冊事件: // 定義 EventHandler 為可以接收物件與傳參的委派 public delegate void EventHandler(object sender, EventArgs e); // 定義傳參可以儲存的內容 public class HandleClickEventArgs : EventArgs { public string Message; } // 創建一個按鍵並為其創建一個可以註冊事件的委派 public class Button { public string name; public Button(string _name) { name = _name; } // 在物件內部定義一個委派存放事件 private event EventHandler Click; public notify() { // 執行委派 // Handler.Invoke(this, e); Handler(this, e); } } // 註冊事件的傳參需與委派的傳參一致 private static void Click(object sender, EventArgs e) { Console.WriteLine( sender.GetType() + " " + (e as HandleClickEventArgs)?.Message ); } public void static Main(string[] args) { EventArgs = new HandleClikEventArgs() { Message = "is clicked!" }; Button button = new Button("TV Remote Button"); button.Click += new EventHandler(Click); button.Click += Click; button.Click += delegate { Console.WriteLine("Check! Check!"); }; button.Click += (sender, e) => Console.WriteLine( (sender as Button)?.name + " " + (e as HandleClickEventArgs)?.Message ); button.notify(e); // Button is clicked! // Button is clicked! // Check! Check! // TV Remote Button is clicked! } 注意到 Button 內有一個委派被定義為 delegate void function(object sender, EventArgs e),並被命名為 Click,故我們會向它註冊事件。 在 C#1 中,完整的寫法是: button.Click += new EventHandler(Click); 在 C#2,透過 Method group conversion 可將其簡化成(條件是兩者的傳參要相同): button.Click += Click; 如果方法較簡單也可以透過匿名方法(anonymous method)註冊: 需要傳參時: button.Click += delegate(object sender, EventArgs e) { Console.WriteLine("Click!"); }; 不需要傳參時: button.Click += delegate { Console.WriteLine("Click!"); }; 在 C#3 中,可以透過 Lambda 表達式來創建。 button.Click += (sender, e) => Console.WriteLine("Click!");

<span title='2023-02-26 19:24:56 +0800 +0800'>February 26, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;Rain Hu