首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 擴展方法進階實戰:ToString(string format) 動態格式化

     編輯:關於C#

前言

ToString() 僅能提供單一格式輸出。本文逐步打造 ToString(string format) 擴展,支援屬性佔位符、格式化字串、巢狀屬性存取、集合屬性操作,讓任何物件都能像 DateTime、int 一樣靈活格式化輸出。

版本演進

V1:簡單屬性名替換 (有缺陷)

public static string ToString1(this object obj, string format)
{
    var props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var pattern = string.Join("|", props.Select(p => p.Name));
    return Regex.Replace(format, pattern, m => 
    {
        var val = props.First(p => p.Name == m.Value).GetValue(obj);
        return val?.ToString() ?? "";
    });
}
// 問題:無法區分「常量文字」與「屬性佔位符」

V2:中括號佔位符語法 [PropertyName]

public static string ToString2(this object obj, string format)
{
    var props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    return Regex.Replace(format, @"\[(?<Name>[^\]]+)\]", m =>
    {
        var prop = props.FirstOrDefault(p => p.Name == m.Groups["Name"].Value);
        if (prop == null) return m.Value; // 保留原樣
        var val = prop.GetValue(obj);
        return val?.ToString() ?? "";
    });
}
// 用法: obj.ToString2("Name: [Name], Age: [Age]")

V3:支援格式化字串 [Property:Format]

public static string ToString3(this object obj, string format)
{
    var props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    return Regex.Replace(format, @"\[(?<Name>[^\[\]:]+)(?::\s*(?<Format>[^\[\]:]+))?\]", m =>
    {
        var propName = m.Groups["Name"].Value;
        var fmt = m.Groups["Format"].Value;
        var prop = props.FirstOrDefault(p => p.Name == propName);
        if (prop == null) return m.Value;
        var val = prop.GetValue(obj);
        return string.IsNullOrEmpty(fmt) 
            ? val?.ToString() ?? "" 
            : string.Format("{0:" + fmt + "}", val);
    });
}
// 用法: obj.ToString3("Birth: [Birthday:yyyy-MM-dd]")

V4:支援巢狀屬性 [Parent.Child.Property]

public static string ToString4(this object obj, string format)
{
    return Regex.Replace(format, @"\[(?<Name>[^\[\]:]+)(?::\s*(?<Format>[^\[\]:]+))?\]", m =>
    {
        var path = m.Groups["Name"].Value.Split('.');
        var fmt = m.Groups["Format"].Value;
        object current = obj;
        try
        {
            foreach (var propName in path)
                current = current.GetType().GetProperty(propName)?.GetValue(current);
        }
        catch { return m.Value; }
        return string.IsNullOrEmpty(fmt) 
            ? current?.ToString() ?? "" 
            : string.Format("{0:" + fmt + "}", current);
    });
}
// 用法: person.ToString4("Grandson: [Son.Son.Name], Born: [Son.Son.Birthday:yyyy年MM月dd日]")

V5:支援集合操作 [Collection.Count]、[Collection.Property]

public static string ToString5(this object obj, string format)
{
    return Regex.Replace(format, @"\[(?<Name>[^\[\]:]+)(?::\s*(?<Format>[^\[\]:]+))?\]", m =>
    {
        var path = m.Groups["Name"].Value.Split('.');
        var fmt = m.Groups["Format"].Value;
        object current = obj;
        try
        {
            foreach (var propName in path)
                current = current.GetType().GetProperty(propName)?.GetValue(current);
        }
        catch { return m.Value; }

        if (string.IsNullOrEmpty(fmt)) return current?.ToString() ?? "";

        // 集合特殊操作:.Count, .Name 等
        if (fmt.StartsWith("."))
        {
            var op = fmt.Substring(1);
            var enumerable = current as System.Collections.IEnumerable;
            if (enumerable == null) return m.Value;
            var items = enumerable.Cast<object>().ToArray();
            return op switch
            {
                "Count" => items.Length.ToString(),
                _ => string.Join(", ", items.Select(i => i.GetType().GetProperty(op)?.GetValue(i)?.ToString() ?? ""))
            };
        }
        return string.Format("{0:" + fmt + "}", current);
    });
}
// 用法: person.ToString5("Friends: [Friends.Count], Names: [Friends.Name]")

輔助擴展:屬性值存取

public static object GetPropertyValue(this object obj, string propertyName)
    => obj?.GetType().GetProperty(propertyName)?.GetValue(obj);

效能優化建議

  1. 快取 PropertyInfo:使用 ConcurrentDictionary<Type, PropertyInfo[]> 快取反射結果。
  2. 預編譯正則:RegexOptions.Compiled 或靜態 Regex 實例。
  3. Expression Tree 編譯:極高效能場景可動態編譯 Lambda 取代反射。

適用場景與限制

優點缺點
零侵入,任意物件即用 反射效能較差,不適合高頻呼叫
格式字串外部化,支援多語系/樣板 編譯期無法檢查屬性名拼寫錯誤
支援巢狀、集合、格式化等豐富語法 複雜邏輯難以除錯

結語

動態 ToString(format) 是原型開發、日誌記錄、報表樣板、序列化除錯的利器。生產環境高頻路徑建議改用原生 ToString() 覆寫或原始碼產生器 (Source Generator) 實現編譯期優化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved