c# 扩展方法奇思妙用高级篇五:ToString(string format) 扩展
在.Net中,System.Object.ToString()是用得最多的方法之一,ToString()方法在Object類中被定義為virtual,Object類給了它一個默認實現:
1?????public?virtual?string?ToString()2?????{
3?????????return?this.GetType().ToString();
4?????}
?.Net中原生的class或struct,如int,DateTime等都對它進行重寫(override),以讓它返回更有價值的值,而不是類型的名稱。合理重寫的ToString()方法中編程、調試中給我們很大方便。但終究一個類只有一個ToString()方法,不能滿足我們多樣化的需求,很多類都對ToString()進行了重載。如下:
1?????string?dateString?=?DateTime.Now.ToString("yyyy");??//20092?????string?intString?=?10.ToString("d4");??//0010
?int、DateTime都實現了ToString(string format)方法,極大方便了我們的使用。
?對于我們自己定義的類型,我們也應該提供一個合理的ToString()重寫,如果能夠提供再提供一個ToString(string format),就會令我們后期的工作更加簡單。試看以下類型:?
?1?????public?class?People?2?????{
?3?????????private?List<People>?friends?=?new?List<People>();
?4?
?5?????????public?int?Id?{?get;?set;?}
?6?????????public?string?Name?{?get;?set;?}
?7?????????public?DateTime?Brithday?{?get;?set;?}
?8?????????public?People?Son?{?get;?set;?}
?9?????????public?People[]?Friends?{?get?{?return?friends.ToArray();?}?}
10?
11?????????public?void?AddFriend(People?newFriend)
12?????????{
13?????????????if?(friends.Contains(newFriend))?throw?new?ArgumentNullException("newFriend",?"該朋友已添加");
14?????????????else?friends.Add(newFriend);
15?????????}
16?????????public?override?string?ToString()
17?????????{
18?????????????return?string.Format("Id:?{0},?Name:?{1}",?Id,?Name);
19?????????}
20?????????
21?????}
?一個簡單的類,我們給出一個ToString()重寫,返回包含Id和Name兩個關鍵屬性的字符串。現在我們需要一個ToString(string format)重寫,以滿足以下應用:
1?????People?p?=?new?People?{?Id?=?1,?Name?=?"鶴沖天",?Brithday?=?new?DateTime(1990,?9,?9)?};2?????string?s0?=?p.ToString("Name?生日是?Brithday");?//理想輸出:鶴沖天?生日是?1990-9-9
3?????string?s1?=?p.ToString("編號為:Id,姓名:Name");?//理想輸出:編號為:1,姓名:鶴沖天
?想想怎么實現吧,記住format是可變的,不定使用了什么屬性,也不定進行了怎樣的組合...
?也許一個類好辦,要是我們定義很多類,幾十、幾百個怎么辦?一一實現ToString(string format)會把人累死的。好在我們有擴展方法,我們對object作一擴展ToString(string format),.Net中object是所有的基類,對它擴展后所有的類都會自動擁有了。當然已有ToString(string format)實現的不會,因為原生方法的優先級高,不會被擴展方法覆蓋掉。
?來看如何實現吧(我們會一步一步改進,為區分各個版本,分別擴展為ToString1、ToString2...分別對應版本一、版本二...):
?1?????public?static?string?ToString1(this?object?obj,?string?format)?2?????{
?3?????????Type?type?=?obj.GetType();
?4?????????PropertyInfo[]?properties?=??type.GetProperties(
?5?????????????BindingFlags.GetProperty?|?BindingFlags.Public?|?BindingFlags.Instance);
?6?
?7?????????string[]?names?=?properties.Select(p?=>?p.Name).ToArray();
?8?????????string?pattern?=?string.Join("|",?names);
?9?
10?????????MatchEvaluator?evaluator?=?match?=>
11?????????????{
12?????????????????PropertyInfo?property?=?properties.First(p?=>?p.Name?==?match.Value);
13?????????????????object?propertyValue?=?property.GetValue(obj,?null);
14?????????????????if?(propertyValue?!=?null)?return?propertyValue.ToString();
15?????????????????else?return?"";
16?????????????};
17?????????return?Regex.Replace(format,?pattern,?evaluator);
18?????}
?3~5行通過反射獲取了公有的、實例的Get屬性(如果需要靜態的或私有的,修改第5行中即可),7~8行動態生成一個正則表達式來匹配format,10~16行是匹配成功后的處理。這里用到反射和正則表達式,如果不熟悉不要緊,先調試運行吧,測試一下前面剛提到的應用:
?第一個和我們理想的有點差距,就是日期上,我們應該給日期加上"yyyy-MM-dd"的格式,這個我們稍后改進,我們現在有一個更大的問題:
?如果我們想輸出:“People: Id 1, Name 鶴沖天”,format怎么寫呢?寫成format="People: Id Id, Name Name",這樣沒法處理了,format中兩個Id、兩個Name,哪個是常量,哪個是變量啊?解決這個問題,很多種方法,如使用轉義字符,可是屬性長了不好寫,如format="\B\r\i\t\h\d\a\y Brithday"。我權衡了一下,最后決定采用類似Sql中對字段名的處理方法,在這里就是給變量加上中括號,如下:
1?????People?p2?=?new?People?{?Id?=?1,?Name?=?"鶴沖天",?Brithday?=?new?DateTime(1990,?9,?9)?};2?????string?s2?=?p1.ToString2("People:Id?[Id],?Name?[Name],?Brithday?[Brithday]");
?版本二的實現代碼如下:
?1????public?static?string?ToString2(this?object?obj,?string?format)?2????{
?3????????Type?type?=?obj.GetType();
?4????????PropertyInfo[]?properties?=?type.GetProperties(
?5????????????BindingFlags.GetProperty?|?BindingFlags.Public?|?BindingFlags.Instance);
?6?
?7????????MatchEvaluator?evaluator?=?match?=>
?8????????{
?9????????????string?propertyName?=?match.Groups["Name"].Value;
10????????????PropertyInfo?property?=?properties.FirstOrDefault(p?=>?p.Name?==?propertyName);
11????????????if?(property?!=?null)
12????????????{
13????????????????object?propertyValue?=?property.GetValue(obj,?null);
14????????????????if?(propertyValue?!=?null)?return?propertyValue.ToString();
15????????????????else?return?"";
16????????????}
17????????????else?return?match.Value;
18????????};
19????????return?Regex.Replace(format,?@"\[(?<Name>[^\]]+)\]",?evaluator,?RegexOptions.Compiled);
20????}
?調試執行一下:
?
??與版本一類似,不過這里沒有動態構建正則表達式,因為有了中括號,很容易區分常量和變量,所以我們通過“屬性名”來找“屬性”(對應代碼中第10行)。如果某個屬性找不到,我們會將這“[Name]”原樣返回(對就第17行)。另一種做法是拋出異常,我不建議拋異常,在ToString(string format)是不合乎“常理”的。?
?版本二相對版本一效率有很大提高,主要是因為版本二只使用一個簡單的正則表達式:@"\[(?<Name>[^\]]+)\]"。而版本一中的如果被擴展類的屬性特別多,動態生成的正則表達式會很長,執行起來也會相對慢。
?
?我們現在來解決兩個版本中都存在的時間日期格式問題,把時間日期格式"yyyy-MM-dd"也放入中括號中,測試代碼如下:
1?????People?p3?=?new?People?{?Id?=?1,?Name?=?"鶴沖天",?Brithday?=?new?DateTime(1990,?9,?9)?};2?????string?s3?=?p3.ToString3("People:Id?[Id:?d4],?Name?[Name],?Brithday?[Brithday:?yyyy-MM-dd]");
?版本三實現代碼:
?1?????public?static?string?ToString3(this?object?obj,?string?format)?2?????{
?3?????????Type?type?=?obj.GetType();
?4?????????PropertyInfo[]?properties?=?type.GetProperties(
?5?????????????BindingFlags.GetProperty?|?BindingFlags.Public?|?BindingFlags.Instance);
?6?
?7?????????MatchEvaluator?evaluator?=?match?=>
?8?????????{
?9?????????????string?propertyName?=?match.Groups["Name"].Value;
10?????????????string?propertyFormat?=?match.Groups["Format"].Value;
11?
12?????????????PropertyInfo?propertyInfo?=?properties.FirstOrDefault(p?=>?p.Name?==?propertyName);
13?????????????if?(propertyInfo?!=?null)
14?????????????{
15?????????????????object?propertyValue?=?propertyInfo.GetValue(obj,?null);
16?????????????????if?(string.IsNullOrEmpty(propertyFormat)?==?false)
17?????????????????????return?string.Format("{0:"?+?propertyFormat?+?"}",?propertyValue);
18?????????????????else?return?propertyValue.ToString();
19?????????????}
20?????????????else?return?match.Value;
21?????????};
22?????????string?pattern?=?@"\[(?<Name>[^\[\]:]+)(\s*:\s*(?<Format>[^\[\]:]+))?\]";
23?????????return?Regex.Replace(format,?pattern,?evaluator,?RegexOptions.Compiled);
24?????}
?測試一下,可OK了:
?
??對于簡單的值類型屬性沒問題了,但對于復雜一些類型如,如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)擴展帶來便利性的同時,也會帶來相應的性能損失,兩者很難兼得。
?最后重申下,本系列文章,側重想法,所給的代碼僅供演示、參考,沒有考慮性能、異常處理等,如需實際使用,請自行完善。?
轉載于:https://www.cnblogs.com/ywsoftware/archive/2013/06/09/3128773.html
總結
以上是生活随笔為你收集整理的c# 扩展方法奇思妙用高级篇五:ToString(string format) 扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: String Statistics(20
- 下一篇: ios之mknetworkkit笔记