Utility - Mirror
Mirror 是反射(reflection)相關的萬用工具。封裝了 Type.GetProperties()、跨組件搜尋類別、批次成員覆蓋等常見模式。
Namespace
using ZapLib.Utility;
Members — 遍歷物件屬性
把任意物件當作 key-value 集合迭代:
var user = new
{
Id = 1,
Name = "ZapLib",
Since = new DateTime(2018, 1, 1)
};
foreach (DictionaryEntry m in Mirror.Members(user))
{
Console.WriteLine($"{m.Key} = {m.Value}");
}
輸出:
Id = 1
Name = ZapLib
Since = 2018/1/1 上午 12:00:00
對
IDictionary友善 — 傳入Dictionary<string, object>也能直接迭代。
EachMembers — 帶型別轉換的迭代
每個成員都先轉成你要的型別:
var data = new { name = "ZapLib", count = "42", price = "99.5" };
Mirror.EachMembers<string, string>(data, (key, value) =>
{
Console.WriteLine($"{key}: {value}");
});
或要求數字型別 — 轉不過會給 default(int):
Mirror.EachMembers<string, int>(data, (key, value) =>
{
// name → 0 (字串轉不出 int)
// count → 42
// price → 0 (含小數點)
Console.WriteLine($"{key}: {value}");
});
Assign — 物件合併
把多個物件的同名屬性依序覆蓋到目標物件上:
public class UserSettings
{
public string Language { get; set; } = "en";
public string Theme { get; set; } = "light";
public int FontSize { get; set; } = 14;
}
var defaults = new UserSettings();
var fromConfig = new { Language = "zh-TW", Theme = "dark" };
var fromQuery = new { FontSize = 16 };
UserSettings s = new UserSettings();
Mirror.Assign(ref s, defaults, fromConfig, fromQuery);
// s.Language = "zh-TW" (從 fromConfig 覆蓋)
// s.Theme = "dark" (從 fromConfig 覆蓋)
// s.FontSize = 16 (從 fromQuery 覆蓋)
覆蓋規則:屬性名稱相同才會覆蓋,型別不同會走
Cast.To()嘗試轉型,轉不過則保留原值。
AssignValue — 單一屬性安全覆蓋
public class Config
{
public int Timeout { get; set; }
public string Host { get; set; }
}
Config c = new Config();
Mirror.AssignValue(ref c, "Timeout", "30"); // "30" → 30, OK
Mirror.AssignValue(ref c, "Host", "localhost"); // OK
Mirror.AssignValue(ref c, "Unknown", "value"); // 屬性不存在,靜默忽略
Mirror.AssignValue(ref c, "Timeout", "xxx"); // 轉不過 int,靜默忽略
GetClasses — 全系統搜尋類別
找出當前 AppDomain 內所有衍生自 T 的類別:
// 找所有 Hub 衍生類別
IEnumerable<Type> hubs = Mirror.GetClasses<Hub>();
foreach (var t in hubs)
{
Console.WriteLine(t.FullName);
}
要包含 T 本身?傳 include_self: true:
var all = Mirror.GetClasses<ApiController>(include_self: true);
GetClasses from Assembly
限定在特定 assembly 內搜尋(更快):
Assembly asm = Assembly.GetExecutingAssembly();
var controllers = Mirror.GetClasses<ApiController>(asm);
GetCustomAttributes
從 PropertyInfo 取出指定型別的 Attributes:
public class ModelBook
{
[SQLType(SqlDbType.NVarChar, Size = 50)]
public string Name { get; set; }
}
PropertyInfo prop = typeof(ModelBook).GetProperty("Name");
var attrs = Mirror.GetCustomAttributes<ISQLTypeAttribute>(prop);
foreach (var a in attrs)
{
Console.WriteLine($"{a.GetSQLType()} ({a.Size})");
// 輸出:NVarChar (50)
}
ZapLib 內部就是用這個機制在
SQL類別中讀取[SQLType]並套用到SqlParameter。
Real-World Patterns
Anonymous Object → Dictionary
public static Dictionary<string, object> ToDictionary(object obj)
{
var dict = new Dictionary<string, object>();
foreach (var m in Mirror.Members(obj))
{
dict[m.Key.ToString()] = m.Value;
}
return dict;
}
var d = ToDictionary(new { id = 1, name = "x" });
Plugin Discovery
掃描所有 plugin DLL 找實作某 interface 的類別:
public interface IPlugin
{
void Run();
}
var plugins = Mirror.GetClasses<IPlugin>()
.Select(t => (IPlugin)Activator.CreateInstance(t));
foreach (var p in plugins) p.Run();
Performance Notes
- 反射很慢:每次
GetProperties()都要走 metadata。熱路徑請快取 GetClasses<T>會掃全部 assembly — 在大專案可能耗時。可改用GetClasses<T>(Assembly)限定範圍Assign內部對每個物件都呼叫一次Members()。對巨量物件迭代不適合