[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!");

February 26, 2023 · 2 分鐘 · Rain Hu

[C#] Nullable 可空值類型

1. 簡寫 以後綴 ? 之前接在類型名稱後面,也就是 T? 可以視同為 Nullable<T> 舉例來說,下列四個變數類型名稱完全等價: Nullable<int> Nullable<int32> int? int32 2. 定義 Nullable 的核心程式碼為: public struct Nullable<T> where struct // 類型約束為非空值類型 { private readonly T value; private readonly bool hasValue; public Nullable(T value) { this.value = value; this.hasValue = true; } public bool HasValue { get { return hasValue; } } public T Value { get { if (!hasValue) { threw new InvalidOperationException(); } return value; } } } 由於 Nullable 的空值定義為 hasValue == false, 故以下兩者為等價 int? x = new int?(); int? x = null; 3. 轉換 允許 T 到 Nullable 的隱式轉換 int x = 5; int? y = x; 允許 Nullable 到 T 的顯式轉換 int? x = 5; int y = (int)x; 通過填充 null 值擴展已有功能的過程稱為 提升 lifting。 ...

February 23, 2023 · 3 分鐘 · Rain Hu

[C#] Generic 泛型

1. 數組到泛型集合 數組缺少初始化時大小的彈性 string[] names = new string[4]; // 需指定大小 普通對象集合缺少類型安全 ArrayList names = new ArrayList(); names.AddRange(new object[] { "Rose", "Jack", new int?(5) // 可合法進行編譯, 因為符合 object }); foreach (string name in names) Console.WriteLine(name); // 缺少型別檢查, 在執行隱性類型轉換(int? 轉 string)時會出錯 專用對象集合缺少撰寫程式碼的靈活性(如無法以靜態方式寫通用方法)、且維護成本較高 StringCollection names = new StringCollection(); names.AddRange(new string[] { "Rose", "Jack", "Gozilla" }); static void printNames(StringCollection names) { foreach (string name in names) Console.WriteLine(name); } 泛型可以解決以上問題 在初始化時不須先設定大小 在添加錯誤型別的元素,在編譯時期就會報錯 兼容各種類型,省去撰寫與維護的成本 List<string> names = new List<string>{ "Rose", "Jack", "Gozilla" }; static void printList(List<T> list) { foreach (T item in list) Console.WriteLine(item); } 2. 靜態方法取代建構式 由於在泛型函式調用時,會進行類型推斷,故可以透過靜態方法簡化建構式,可以省去指定泛型類型: // 靜態泛型函式 public static Tuple<T1, T2> CreateTuple(T1 item1, T2 item2) { return new Tuple<T1, T2>(item1, item2); } // 泛型建構式 var tuple1 = new Tuple<string, int>("Jack", 18); var tuple2 = CreateTuple("Jack", 18); // 由於類型推斷, 省去寫泛型型別 3. 類型約束 類型約束可用來限制類型實參的類別: public static void SortList<T>(List<T> items) where T : IComparable<T> { // 因為限定 T 需要繼承 IComparable, 所以可以叫用其 CompareTo 方法 items.Sort((x,y) => x.CompareTo(y)); } 4. 泛型類型的初始化與狀態 List<int> 與 List<string> 雖油桐一個泛型類型 List<T> 限定,但在執行期會被當作兩個不同的類型 範例1. 用 typeof 測試 public static void PrintType<T>() { Console.WriteLine($"typeof(T) = {typeof(T)}"); Console.WriteLine($"typeof(List<T>) = {typeof(List<T>)}"); } public static void Main() { PrintType<string>(); PrintType<int>(); } // typeof(T) = System.String // typeof(List<T>) = System.Collections.Generic.List`1[System.String] // typeof(T) = System.Int32 // typeof(List<T>) = System.Collections.Generic.List`1[System.Int32] 範例2. 檢查靜態域 class GenericCounter<T> { private static int value; static GenericCounter() { Console.WriteLine($"Initializing counter for {typeof(T)}"); } public static void Increment() { value++; } public static void Display() { Console.WriteLine($"Counter for {typeof(T)}: {value}"); } } public static void Main() { GenericCounter<string>.Increment(); GenericCounter<string>.Increment(); GenericCounter<string>.Display(); GenericCounter<int>.Display(); GenericCounter<int>.Increment(); GenericCounter<int>.Display(); } // Initializing counter for System.String // Counter for System.String: 2 // Initializing counter for System.Int32 // Counter for System.Int32: 0 // Counter for System.Int32: 1

February 22, 2023 · 2 分鐘 · Rain Hu

[C#] Yield Return

C# 中的 Yield Return 與 IEnumerable<T> 在使用過 C# 與 Java 兩種語言之後會發現,Java 為了跨平台的特性,鮮少修改 VM 規格,相較 .NET 為了語法簡潔,編譯器往往會做出讓步,也因此 C# 比起 Java 多了許多語法糖。如 delegate 等,在這邊預留一些內容到下一次寫,今天專注於 yield return 與 IEnumerable。 起源 為何需要 IEnumerable? 1. 目標:依序印出 1 ~ 100 的數字 一般來說,若要做到以上的目標,只需要用到簡單的 for_loop 即可。 static void Main(string[] args) { for (int i = 1; i <= 100; i++) { Console.Write("{0} ", i); } } IEnumerable 是什麼?要做什麼用? IEnumerator 是一種列舉器,它是特化用來專門處理 iteration 的工具。 在 Design Patterns 中有一種設計模式叫作 Iterator,它的目的就是要在: 不需要知道物件的內部細節,即可依序存取內含的每一個元素。 IEnumerator 物件的實作: public class Enumerator1 : IEnumerator<int> { private int _start; private int _end; private int _curr; public Enumerator1(int start, int end) { _start = start; _end = end; this.Reset(); } public int Current { get { return this._curr; } } public void Dispose() { } object System.Collections.IEnumerator.Current { get { return this._curr; } } public bool MoveNext() { this._curr++; return !(this._curr > this._end); } public void Reset() { this._curr = this._start; } } 於是,我們若要遍歷這個物件的內容,只需要: static void Main(string[] args) { Enumerator1 e = new Enumerator1(1, 100); do { Console.Write("{0} ", e.Current); } while (e.MoveNext()); } Iteration/Process 分離 當我們今天不想管 collection 裡每一個物件是怎麼擺的,用什麼結構裝、用什麼邏輯或演算法處理的,只想要依序將安排好的元素拿出來。也就是說,我們想把物件遍歷的(iteration) 與拿到它後要做什麼事(process) 分開,那麼就可以用到 Iterator Pattern。 2. 目標:依序印出 1 ~ 100 中的質數 ...

January 1, 2023 · 3 分鐘 · Rain Hu