[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。 ...

<span title='2023-02-23 21:40:03 +0800 +0800'>February 23, 2023</span>&nbsp;·&nbsp;3 min&nbsp;·&nbsp;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

<span title='2023-02-22 21:39:56 +0800 +0800'>February 22, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;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 中的質數 ...

<span title='2023-01-01 21:40:25 +0800 +0800'>January 1, 2023</span>&nbsp;·&nbsp;3 min&nbsp;·&nbsp;Rain Hu