在 Web 開發中,頻繁需要驗證來自前端的字串參數是否可轉換為 int、bool、DateTime 等類型。標準做法是 int.TryParse(str, out var val),但每次都要宣告 out 變數,且需針對每種類型寫重複代碼。本文利用 C# 擴展方法 + 反射,實現通用 string.Is<T>(),一次解決所有實作 IConvertible 且擁有 TryParse(string, out T) 靜態方法的類型驗證。
using System;
using System.Reflection;
using System.Security;
public static class TypeValidate
{
///
/// 判斷字串是否可轉換為類型 T
/// T 必須:1. 實作 IConvertible 2. 擁有無參建構函數 3. 擁有靜態方法 bool TryParse(string, out T)
///
[SecuritySafeCritical]
public static bool Is<T>(this string value) where T : IConvertible
{
var type = typeof(T);
// TryParse 第二參數為 out T,反射需用 MakeByRefType
var types = new Type[] { typeof(string), type.MakeByRefType() };
var method = type.GetMethod("TryParse", types);
if (method == null)
throw new TryParseException($"Type {type.Name} does not have a suitable TryParse(string, out T) method.");
try
{
// 建立 T 實例作為 out 參數佔位 (TryParse 為靜態方法,實例不影響調用)
T instance = Activator.CreateInstance<T>();
// Invoke 靜態方法,傳入 null 作為 instance
object[] args = new object[] { value, instance };
bool result = (bool)method.Invoke(null, args);
return result;
}
catch (Exception ex)
{
throw new TryParseException($"TryParse invocation failed for {type.Name}.", ex);
}
}
}
public class TryParseException : Exception
{
public TryParseException() : base() { }
public TryParseException(string message) : base(message) { }
public TryParseException(string message, Exception inner) : base(message, inner) { }
}
string input = "123";
if (input.Is<int>()) { /* ... */ }
if ("true".Is<bool>()) { /* ... */ }
if ("2023-12-25".Is<DateTime>()) { /* ... */ }
// 支援所有內建實作 TryParse 的類型:byte, short, long, float, double, decimal, Guid, TimeSpan, Enum 等
if ("42".Is<byte>()) { /* ... */ }
if ("3.14".Is<double>()) { /* ... */ }
if ("Guid".Is<Guid>()) { /* false */ }
where T : IConvertible:縮小適用範圍至內建基礎類型,確保語義清晰。type.MakeByRefType():TryParse 第二參數為 out T,對應 CLR 的 byref 類型,反射查找方法時必須指定 typeof(string), type.MakeByRefType(),否則 GetMethod 返回 null。Activator.CreateInstance<T>():建立實例僅為滿足 Invoke 參數陣列長度,TryParse 為靜態方法,實際不使用該實例。method.Invoke(null, args):靜態方法調用傳 null 作為目標實例。TryParseException:區分「類型不支援」與「轉換失敗」,調用方可選擇捕獲或向上拋出。若自定義結構體 struct MyInt { public static bool TryParse(string s, out MyInt result) { ... } } 實作 IConvertible,同樣可用 str.Is<MyInt>()。
MethodInfo (用 ConcurrentDictionary<Type, MethodInfo>)。int.TryParse 仍是最快。string.Is<T>() 以極少代碼統一了所有基礎類型的字串驗證邏輯,消除重複 TryParse 樣板代碼,提升代碼可讀性與維護性。結合反射動態查找 TryParse,體現了 C# 泛型與反射結合的靈活威力。