標籤:des style blog http color 使用
在開發中,有時候會遇到需要把一個List對象中的某個欄位用一個分隔字元拼成一個字串的情況。比如在SQL語句的in條件中,我們通常需要把List<int>這樣的對象轉換為“1,2,3”這樣的字串,然後作為in的語句傳進去。所以自然而然,可以通過迴圈的方式來拼著個字串,於是可以寫一個下面這樣的通用方法:
private static string GetStringFromList<T>(char seperator, IEnumerable<T> values){ if (seperator == null) return string.Empty; if (values == null && values.Count() == 0) return string.Empty; String result; StringBuilder strBuilder; strBuilder = new StringBuilder(); foreach (T str in values) { strBuilder.Append(str.ToString()); strBuilder.Append(seperator); } result = strBuilder.ToString().TrimEnd(seperator); return result;}
方法其實很簡單,首先建立一個StringBuilder,然後再往裡面Append資料,最後把最後多餘的最後一個分隔字元去除。
後來發現BCL中string類型提供了現成的string.Join方法,該方法的功能和上面的方法相同。於是很好奇,想看看BCL中是如何?這麼一個簡單的功能的,由於BCL的大部分代碼已經開源,您可以使用Reflector這個工具查看,我之前就是使用這個工具,但是最近看到了微軟的Reference Source 這個網站,可以線上查看原始碼,比如string類的實現如下,您可以看到諸如string的GetHashCode是如何?的等等, 這裡我們回到我們想要查看的Join方法上來,其實現如下:
[ComVisible(false)]public static String Join<T>(String separator, IEnumerable<T> values){ if (values == null) throw new ArgumentNullException("values"); Contract.Ensures(Contract.Result<String>() != null); Contract.EndContractBlock(); if (separator == null) separator = String.Empty; using (IEnumerator<T> en = values.GetEnumerator()) { if (!en.MoveNext()) return String.Empty; StringBuilder result = StringBuilderCache.Acquire(); if (en.Current != null) { // handle the case that the enumeration has null entries // and the case where their ToString() override is broken string value = en.Current.ToString(); if (value != null) result.Append(value); } while (en.MoveNext()) { result.Append(separator); if (en.Current != null) { // handle the case that the enumeration has null entries // and the case where their ToString() override is broken string value = en.Current.ToString(); if (value != null) result.Append(value); } } return StringBuilderCache.GetStringAndRelease(result); }}
代碼是不是很簡單。對比之前手動實現的方法,發現自己寫的代碼看起來很挫,這個就是差距,String的Join方法中我們可以看到一下幾個地方值得注意:
- 在方法的開始處,使用了Contract 這個類來進行驗證協助代碼的編寫,這個在之前的文章中有所介紹;還有就是在方法開始處做必要的參數合法性驗證;在方法中及時判斷,及時返回。
- 在實現中,使用了列舉程式,C#中的foreach語句其實就是這種列舉程式的文法糖,所以這裡沒有什麼好說的,值得一提的是在while迴圈中的判斷語句while(en.MoveNext) 很好的避免了我們方法中在字串末尾添加多餘的字串,最後還要調用TrimEnd的這種無謂的記憶體開銷。這其實也是do{…}while(..),和while(…){…}這兩種迴圈體的差異體現。
- 實現中,沒有直接new直接分配StringBuilder,在返回字串時也沒有直接使用ToString方法,而是使用了StringBuilderCache這個類,這個在之前翻譯的.NET程式的效能要領和最佳化建議 這篇文章中有所介紹。
這個類一看就是對StringBuilder的緩衝,因為對於一些小的字串,建立StringBuilder也是一筆開銷。StringBuilder的實現如下:
// ==++==// // Copyright (c) Microsoft Corporation. All rights reserved.// // ==--==/*============================================================**** Class: StringBuilderCache**** Purpose: provide a cached reusable instance of stringbuilder** per thread it‘s an optimisation that reduces the ** number of instances constructed and collected.**** Acquire - is used to get a string builder to use of a ** particular size. It can be called any number of ** times, if a stringbuilder is in the cache then** it will be returned and the cache emptied.** subsequent calls will return a new stringbuilder.**** A StringBuilder instance is cached in ** Thread Local Storage and so there is one per thread**** Release - Place the specified builder in the cache if it is ** not too big.** The stringbuilder should not be used after it has ** been released.** Unbalanced Releases are perfectly acceptable. It** will merely cause the runtime to create a new ** stringbuilder next time Acquire is called.**** GetStringAndRelease** - ToString() the stringbuilder, Release it to the ** cache and return the resulting string**===========================================================*/using System.Threading; namespace System.Text{ internal static class StringBuilderCache { // The value 360 was chosen in discussion with performance experts as a compromise between using // as litle memory (per thread) as possible and still covering a large part of short-lived // StringBuilder creations on the startup path of VS designers. private const int MAX_BUILDER_SIZE = 360; [ThreadStatic] private static StringBuilder CachedInstance; public static StringBuilder Acquire(int capacity = StringBuilder.DefaultCapacity) { if(capacity <= MAX_BUILDER_SIZE) { StringBuilder sb = StringBuilderCache.CachedInstance; if (sb != null) { // Avoid stringbuilder block fragmentation by getting a new StringBuilder // when the requested size is larger than the current capacity if(capacity <= sb.Capacity) { StringBuilderCache.CachedInstance = null; sb.Clear(); return sb; } } } return new StringBuilder(capacity); } public static void Release(StringBuilder sb) { if (sb.Capacity <= MAX_BUILDER_SIZE) { StringBuilderCache.CachedInstance = sb; } } public static string GetStringAndRelease(StringBuilder sb) { string result = sb.ToString(); Release(sb); return result; } }}
這裡面對StringBuilder的建立和字串擷取進行了緩衝。 代碼的注釋很清楚,這裡就不多講了。
.NET的原始碼大部分都可以直接看了,以前可以使用Reflector進行查看,現在Reference Source 這個網站可以線上查看原始碼以及詳細的注釋資訊,看看代碼對自己的提高還是挺有協助的。