對於簡單的值類型屬性沒問題了,但對於復雜一些類型如,如People的屬性Son(Son就是兒子,我一開始寫成了Sun),他也是一個People類型,他也有屬性的,而且他也可能有Son...
先看下調用代碼吧:
1 People p4 = new People { Id = 1, Name = "鶴沖天", Brithday = new DateTime(1990, 9, 9) };
2 p4.Son = new People { Id = 2, Name = "鶴小天", Brithday = new DateTime(2015, 9, 9) };
3 p4.Son.Son = new People { Id = 3, Name = "鶴微天", Brithday = new DateTime(2040, 9, 9) };
4 string s4 = p4.ToString4("[Name] 的孫子 [Son.Son.Name] 的生日是:[Son.Son.Brithday: yyyy年MM月dd日]。");
“鶴沖天”也就是我了,有個兒子叫“鶴小天”,“鶴小天”有個兒子,也就是我的孫子“鶴微天”。哈哈,祖孫三代名字都不錯吧(過會先把小天、微天這兩個名字注冊了)!主要看第4行,format是怎麼寫的。下面是版本四實現代碼,由版本三改進而來:
1 public static string ToString4(this object obj, string format)
2 {
3 MatchEvaluator evaluator = match =>
4 {
5 string[] propertyNames = match.Groups["Name"].Value.Split('.');
6 string propertyFormat = match.Groups["Format"].Value;
7
8 object propertyValue = obj;
9 try
10 {
11 foreach (string propertyName in propertyNames)
12 propertyValue = propertyValue.GetPropertyValue(propertyName);
13 }
14 catch
15 {
16 return match.Value;
17 }
18
19 if (string.IsNullOrEmpty(format) == false)
20 return string.Format("{0:" + propertyFormat + "}", propertyValue);
21 else return propertyValue.ToString();
22 };
23 string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
24 return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
25 }
為了反射獲取屬性方法,用到了GetPropertyValue擴展如下(版本三的實現用上這個擴展會更簡潔)(考慮性能請在此方法加緩存):
1 public static object GetPropertyValue(this object obj, string propertyName)
2 {
3 Type type = obj.GetType();
4 PropertyInfo info = type.GetProperty(propertyName);
5 return info.GetValue(obj, null);
6 }
先執行,再分析:
執行正確! 版本四,8~17行用來層層獲取屬性。也不太復雜,不多作解釋了。說明一下,版本四是不完善的,沒有做太多處理。
我們最後再來看一下更復雜的應用,Peoplee有FrIEnds屬性,這是一個集合屬性,我們想獲取朋友的個數,並列出朋友的名字,如下:
1 People p5 = new People { Id = 1, Name = "鶴沖天"};
2 p5.AddFrIEnd(new People { Id = 11, Name = "南霸天" });
3 p5.AddFrIEnd(new People { Id = 12, Name = "日中天" });
4 string s5 = p5.ToString5("[Name] 目前有 [Friends: .Count] 個朋友:[FrIEnds: .Name]。");
注意,行4中的Count及Name前都加了個小點,表示是將集合進行操作,這個小點是我看著方便自己定義的。再來看實現代碼,到版本五了:
1 public static string ToString5(this object obj, string format)
2 {
3 MatchEvaluator evaluator = match =>
4 {
5 string[] propertyNames = match.Groups["Name"].Value.Split('.');
6 string propertyFormat = match.Groups["Format"].Value;
7
8 object propertyValue = obj;
9
10 try
11 {
12 foreach (string propertyName in propertyNames)
13 propertyValue = propertyValue.GetPropertyValue(propertyName);
14 }
15 catch
16 {
17 return match.Value;
18 }
19
20 if (string.IsNullOrEmpty(propertyFormat) == false)
21 {
22 if (propertyFormat.StartsWith("."))
23 {
24 string subPropertyName = propertyFormat.Substring(1);
25 IEnumerable<object> obJS = ((IEnumerable)propertyValue).Cast<object>();
26 if (subPropertyName == "Count")
27 return obJS.Count().ToString();
28 else
29 {
30 string[] subPropertIEs = obJS.Select(
31 o => o.GetPropertyValue(subPropertyName).ToString()).ToArray();
32 return string.Join(", ", subPropertIEs);
33 }
34 }
35 else
36 return string.Format("{0:" + propertyFormat + "}", propertyValue);
37 }
38 else return propertyValue.ToString();
39 };
40 string pattern = @"\[(?<Name>[^\[\]:]+)(\s*[:]\s*(?<Format>[^\[\]:]+))?\]";
41 return Regex.Replace(format, pattern, evaluator, RegexOptions.Compiled);
42 }
執行結果:
比較不可思議吧,下面簡單分析一下。行22~行33是對集合進行操作的相關處理,這裡只是簡單實現了Count,當然也可以實現Min、Max、Sum、Average等等。“.Name”這個表示方法不太好,這裡主要是為了展示,大家能明白了就好。
就寫到這裡吧,版本六、版本七...後面還很多,當然一個比一個離奇,不再寫了。給出五個版本,版本一存在問題,主要看後三個版本,給出多個版本是為滿足不同朋友的需要,一般來說版本三足夠,對於要求比較高,追求新技術的朋友,我推薦版本四、五。要求更高的,就是沒給出的六、七...了。
ToString(string format)擴展帶來便利性的同時,也會帶來相應的性能損失,兩者很難兼得。
最後重申下,本系列文章,側重想法,所給的代碼僅供演示、參考,沒有考慮性能、異常處理等,如需實際使用,請自行完善。