首页 > 代码库 > BCL中String.Join的实现

BCL中String.Join的实现

在开发中,有时候会遇到需要把一个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 = http://www.mamicode.com/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 = http://www.mamicode.com/en.Current.ToString();>if (value != null)                    result.Append(value);            }        }        return StringBuilderCache.GetStringAndRelease(result);    }}

代码是不是很简单。对比之前手动实现的方法,发现自己写的代码看起来很挫,这个就是差距,String的Join方法中我们可以看到一下几个地方值得注意:

  1. 在方法的开始处,使用了Contract 这个类来进行验证协助代码的编写,这个在之前的文章中有所介绍;还有就是在方法开始处做必要的参数合法性验证;在方法中及时判断,及时返回。
  2. 在实现中,使用了枚举器,C#中的foreach语句其实就是这种枚举器的语法糖,所以这里没有什么好说的,值得一提的是在while循环中的判断语句while(en.MoveNext) 很好的避免了我们方法中在字符串末尾添加多余的字符串,最后还要调用TrimEnd的这种无谓的内存开销。这其实也是do{…}while(..),和while(…){…}这两种循环体的差异体现。
  3. 实现中,没有直接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 这个网站可以在线查看源代码以及详细的注释信息,看看代码对自己的提高还是挺有帮助的。