「靈魂,不屬於肉身;邏輯,也不該屬於型別。」
我們都知道,肉體只是外殼,靈魂才是本質。人如此,程式亦然。在軟體的世界裡,邏輯就像靈魂,它決定了一段程式「該怎麼思考」;而型別(Type)就像肉體,負責「具體地呈現」
在泛型出現以前,我們若想讓容器能「存放不同型別」的資料通常會用 object 來實作
1 2 3 4 5 6 7 ArrayList list = new ArrayList(); list.Add(123 ); list.Add("ABC" ); int n = (int )list[0 ]; int m = (int )list[1 ];
這樣雖然「兼容」了所有型別,但有兩個致命缺點
你要手動轉型 (casting),而且有風險(可能轉錯)
編譯器無法幫你檢查型別錯誤,只能等執行時爆掉
泛型 (Generics) 是一種「延後決定型別」的程式設計方法,它讓你寫出對型別沒有限制,但仍然安全可重用的程式碼,也就是說在不同型別之間共用邏輯,但又不放棄型別資訊
1 2 3 List<int > list = new List<int >(); list.Add(123 );
✅ 泛型同樣「通用」,但同時「型別安全」,也讓編譯器能幫你防錯
泛型的核心概念叫「型別參數化(Type Parameterization)」 意思是說,就像函式可以接收不同的「值」參數,泛型則能接收不同的「型別」作為參數,這讓你的程式不再依賴某一種固定型別,而是變得「對型別開放、對邏輯封閉」,換句話說,你把邏輯抽出來,讓它在不同型別之間自由穿梭
有沒有那種時候,你發現自己在寫幾乎相同的程式碼,唯一的差別只是處理的資料型別不同
在物件導向程式設計中,我們追求的是 DRY 原則(Don’t Repeat Yourself) ,但傳統的繼承和多型無法解決「邏輯相同但型別不同」的問題。泛型的出現就是為了解決這個根本矛盾
泛型可以為您做到
邏輯抽象化 :將演算法從具體型別中抽離出來
型別延遲綁定 :在使用時才決定具體要處理什麼型別
編譯時期特化 :編譯器為每個型別產生專用的程式碼
資料驗證器 不同型別的資料驗證邏輯
❌ 重複程式碼的做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class StringValidator { private readonly List<Func<string , (bool IsValid, string Error)>> _rules = new (); public StringValidator AddRule (Func<string , (bool , string )> rule) { _rules.Add(rule); return this ; } public ValidationResult Validate (string value ) { foreach (var rule in _rules) { var (isValid, error) = rule(value ); if (!isValid) return ValidationResult.Fail(error); } return ValidationResult.Success(); } } public class IntValidator { private readonly List<Func<int , (bool IsValid, string Error)>> _rules = new (); public IntValidator AddRule (Func<int , (bool , string )> rule) { _rules.Add(rule); return this ; } public ValidationResult Validate (int value ) { foreach (var rule in _rules) { var (isValid, error) = rule(value ); if (!isValid) return ValidationResult.Fail(error); } return ValidationResult.Success(); } }
✅ 泛型大哥登場!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Validator <T >{ private readonly List<Func<T, (bool IsValid, string Error)>> _rules = new (); public Validator<T> AddRule (Func<T, (bool , string )> rule) { _rules.Add(rule); return this ; } public ValidationResult Validate (T value ) { foreach (var rule in _rules) { var (isValid, error) = rule(value ); if (!isValid) return ValidationResult.Fail(error); } return ValidationResult.Success(); } } public class ValidationResult { public bool IsValid { get ; private set ; } public string ErrorMessage { get ; private set ; } public static ValidationResult Success () => new () { IsValid = true }; public static ValidationResult Fail (string error ) => new () { IsValid = false , ErrorMessage = error }; }
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void Main (){ var stringValidator = new Validator<string >() .AddRule(s => s?.Length > 0 ? (true , "" ) : (false , "字串不能為空" )) .AddRule(s => s?.Length <= 50 ? (true , "" ) : (false , "字串長度不能超過50" )); Console.WriteLine(stringValidator.Validate("Hello World" ).IsValid); Console.WriteLine(stringValidator.Validate("" ).ErrorMessage); var intValidator = new Validator<int >() .AddRule(i => i >= 0 ? (true , "" ) : (false , "數值必須大於等於0" )) .AddRule(i => i <= 100 ? (true , "" ) : (false , "數值不能超過100" )); Console.WriteLine(intValidator.Validate(50 ).IsValid); Console.WriteLine(intValidator.Validate(-10 ).ErrorMessage); var dateValidator = new Validator<DateTime>() .AddRule(d => d > DateTime.Now ? (true , "" ) : (false , "日期必須是未來時間" )) .AddRule(d => d.Year <= 2030 ? (true , "" ) : (false , "日期不能超過2030年" )); Console.WriteLine(dateValidator.Validate(DateTime.Now.AddDays(1 )).IsValid); }
鏈式建構器模式 (Query Builder)
靈活構建查詢,比較快速簡單的做法可以自己手製查詢建構器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class QueryBuilder <T >{ private readonly List<Func<T, bool >> _conditions = new (); private readonly List<Func<T, object >> _orderByKeys = new (); public QueryBuilder <T > Where (Func<T, bool > condition ) { _conditions.Add(condition); return this ; } public QueryBuilder <T > OrderBy <TKey >(Func<T, TKey> keySelector ) { _orderByKeys.Add(item => keySelector(item)); return this ; } public IEnumerable<T> Execute (IEnumerable<T> source ) { var result = source.AsEnumerable(); foreach (var condition in _conditions) { result = result.Where(condition); } if (_orderByKeys.Any()) { result = result.OrderBy(_orderByKeys.First()); } return result; } } void Main (){ var players = new [] { new { Name = "Alice" , Level = 50 , Score = 1200 }, new { Name = "Bob" , Level = 30 , Score = 800 }, new { Name = "Charlie" , Level = 70 , Score = 2000 } }; var builder = new QueryBuilder<Player>(); if (levelFilter.HasValue) builder.Where(p => p.Level >= levelFilter.Value); if (scoreFilter.HasValue) builder.Where(p => p.Score >= scoreFilter.Value); if (sortBy == "Score" ) builder.OrderBy(p => p.Score); else if (sortBy == "Level" ) builder.OrderBy(p => p.Level); var highLevelPlayers = builder.Execute(players); foreach (var player in highLevelPlayers) { Console.WriteLine($"{player.Name} : Lv.{player.Level} , Score: {player.Score} " ); } }
這是泛型的另一個核心應用:將「演算法邏輯」與「資料型別」徹底分離
Repository —— 資料存取的萬用倉庫 Repository(儲存庫)是一種常見於 DDD(Domain-Driven Design) 或企業架構的設計模式。它的目的是把「資料存取邏輯(CRUD)」從業務邏輯中分離出來,我們希望能用同樣的方式操作「使用者(User)」或「產品(Product)」資料,而不需要為每種資料重寫一份 CRUD
泛型在這裡的角色是:
「讓一個資料操作模板(Repository)能處理任意實體型別。」
這樣就可以
用同樣的方法(Add, Remove, GetAll)操作不同資料表
保證所有資料存取都一致
方便後期切換儲存方式(例如從記憶體 → 資料庫)
實作 InMemoryRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 void Main (){ var userRepo = new InMemoryRepository<User>(); var alice = new User() { Name = "Alice" }; var bob = new User() { Name = "Bob" }; userRepo.Add(alice); userRepo.Add(bob); userRepo.GetAll().Dump(); userRepo.Remove(bob); userRepo.GetAll().Dump(); } public interface IRepository <T >{ void Add (T item ) ; void Remove (T item ) ; IEnumerable<T> GetAll () ; } public class InMemoryRepository <T > : IRepository <T >{ private readonly List<T> storage = new (); public void Add (T item ) { Console.WriteLine($"新增 {typeof (T).Name} : {item} " ); storage.Add(item); } public IEnumerable<T> GetAll () { return storage; } public void Remove (T item ) { Console.WriteLine($"移除 {typeof (T).Name} : {item} " ); storage.Remove(item); } } public class User { public string Name { get ; set ; } public override string ToString () => Name; } public class Product { public string Name { get ; set ; } public decimal Price { get ; set ; } public override string ToString () => $"{Name} (${Price} )" ; }
Result —— 一致的回傳結果容器 如果我們希望統一處理「成功」與「失敗」的情況,而不使用例外(Exception),這時就會用到 Result 模式,我們希望讓「回傳結果」同時包含
成功與否(IsSuccess)
錯誤訊息(Error)
成功時的資料(Value)
泛型的角色讓 Result 可以包裝任何型別的結果,例如:
Result:運算結果
Result:資料查詢結果
Result:API 回覆內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void Main (){ var cal = new Calculator(); cal.Divide(3 ,0 ).ToString().Dump(); cal.Divide(3 ,2 ).ToString().Dump(); } public class Error { public string Code { get ; } public string Message { get ; } public Error (string code, string message ) { Code = code; Message = message; } public override string ToString () => $"{Code} : {Message} " ; } public class Result <T >{ public bool IsSuccess { get ; } public Error Error { get ; } public T Value { get ; } private Result (T value ) { Value = value ; IsSuccess = true ; } private Result (Error error ) { Error = error; IsSuccess = false ; } public static Result<T> Success (T value ) => new (value ); public static Result<T> Failure (string code, string message ) => new (new Error(code, message)); public override string ToString () => IsSuccess ? $"✅ Success: {Value} " : $"❌ Error: {Error} " ; } public class Calculator { public Result<double > Divide (double a, double b ) { if (b == 0 ) return Result<double >.Failure("Code_Zero" ,"除數不可以為0!!" ); return Result<double >.Success(a/b); } }
在人生中,我們常太早「決定型別」,太早定義自己是什麼人、適合什麼工作、應該走哪條路 但就像泛型的精神一樣,型別可以延後決定。重要的,是你背後的邏輯與原則!
你學會「學習的方法」而不是「某一門技能」,那就像你寫了一個 Learn 泛型函式 ,未來不管學鋼琴、學程式、學語言,你都能套用同一套思考邏輯
1 2 3 4 5 6 7 public class Learner <T >{ public void Learn (T subject ) { Console.WriteLine($"正在學習 {typeof (T).Name} ..." ); } }
學習「如何學習」,而不是只學「某個東西」。這是泛型思維!