From 8be8087a9df9666ee70a2ba9c24a967bceba9632 Mon Sep 17 00:00:00 2001 From: Peter Jas Date: Sun, 31 Jan 2016 23:04:28 +0000 Subject: [PATCH 1/2] Adds String.Join overload to accept char separator. This adds five additional overload signatures for string.Join to accept char separator. Since the implementation of: ```c# Join(String separator, String[] value, int startIndex, int count) ``` relies on `UnSafeCharBuffer.AppendString(string)` (which is not exposed to user land), I have added a char substitute for same. Also removed an additional check for `separator==null`, which wasn't present in `Join(String separator, IEnumerable values)` either. `StringBuilder.Append` and `UnSafeCharBuffer.AppendString` both handle this case explicitly. --- src/mscorlib/model.xml | 5 + .../src/System/String.Manipulation.cs | 194 +++++++++++++++++- src/mscorlib/src/System/UnSafeCharBuffer.cs | 25 ++- 3 files changed, 213 insertions(+), 11 deletions(-) diff --git a/src/mscorlib/model.xml b/src/mscorlib/model.xml index 001e330961e4..01929f738e99 100644 --- a/src/mscorlib/model.xml +++ b/src/mscorlib/model.xml @@ -7212,6 +7212,11 @@ + + + + + diff --git a/src/mscorlib/src/System/String.Manipulation.cs b/src/mscorlib/src/System/String.Manipulation.cs index d21c06899bb3..98db0fe2e933 100644 --- a/src/mscorlib/src/System/String.Manipulation.cs +++ b/src/mscorlib/src/System/String.Manipulation.cs @@ -541,16 +541,27 @@ public String Insert(int startIndex, String value) } return result; } - - // Joins an array of strings together as one string with a separator between each original string. + + // Joins an array of strings together as one string with a string separator between each original string. // public static String Join(String separator, params String[] value) { - if (value==null) + if (value == null) throw new ArgumentNullException("value"); Contract.EndContractBlock(); return Join(separator, value, 0, value.Length); } + // Joins an array of strings together as one string with a char separator between each original string. + // + public static String Join(Char separator, params String[] value) { + if (value == null) + throw new ArgumentNullException("value"); + + return Join(separator, value, 0, value.Length); + } + + // Joins an object array of strings together as one string with a string separator between each original string. + // [ComVisible(false)] public static string Join(string separator, params object[] values) { @@ -584,6 +595,41 @@ public static string Join(string separator, params object[] values) return StringBuilderCache.GetStringAndRelease(result); } + // Joins an object array of strings together as one string with a char separator between each original string. + // + [ComVisible(false)] + public static String Join(Char separator, params Object[] values) { + if (values == null) + throw new ArgumentNullException("values"); + + if (values.Length == 0 || values[0] == null) + return String.Empty; + + string firstString = values[0].ToString(); + + if (values.Length == 1) + { + return firstString ?? string.Empty; + } + + StringBuilder result = StringBuilderCache.Acquire(); + result.Append(firstString); + + for (int i = 1; i < values.Length; i++) + { + result.Append(separator); + object value = values[i]; + if (value != null) + { + result.Append(value.ToString()); + } + } + + return StringBuilderCache.GetStringAndRelease(result); + } + + // Joins a generic IEnumerable together as one string with a string separator between each original string. + // [ComVisible(false)] public static String Join(String separator, IEnumerable values) { @@ -635,6 +681,46 @@ public static String Join(String separator, IEnumerable values) } } + // Joins a generic IEnumerable together as one string with a char separator between each original string. + // + [ComVisible(false)] + public static String Join(Char separator, IEnumerable values) + { + if (values == null) + throw new ArgumentNullException("values"); + Contract.Ensures(Contract.Result() != null); + Contract.EndContractBlock(); + + using(IEnumerator en = values.GetEnumerator()) + { + if (!en.MoveNext()) + return String.Empty; + + StringBuilder result = StringBuilderCache.Acquire(); + T currentValue = en.Current; + + if (currentValue != null) + { + result.Append(currentValue.ToString()); + } + + while (en.MoveNext()) + { + result.Append(separator); + currentValue = en.Current; + + if (currentValue != null) + { + result.Append(currentValue.ToString()); + } + } + + return StringBuilderCache.GetStringAndRelease(result); + } + } + + // Joins a string IEnumerable together as one string with a string separator between each original string. + // [ComVisible(false)] public static String Join(String separator, IEnumerable values) { if (values == null) @@ -665,7 +751,39 @@ public static String Join(String separator, IEnumerable values) { } } - // Joins an array of strings together as one string with a separator between each original string. + // Joins a string IEnumerable together as one string with a char separator between each original string. + // + [ComVisible(false)] + public static String Join(Char separator, IEnumerable values) { + if (values == null) + throw new ArgumentNullException("values"); + Contract.Ensures(Contract.Result() != null); + Contract.EndContractBlock(); + + using(IEnumerator en = values.GetEnumerator()) { + if (!en.MoveNext()) + return String.Empty; + + String firstValue = en.Current; + + if (!en.MoveNext()) { + // Only one value available + return firstValue ?? String.Empty; + } + + // Null separator and values are handled by the StringBuilder + StringBuilder result = StringBuilderCache.Acquire(); + result.Append(firstValue); + + do { + result.Append(separator); + result.Append(en.Current); + } while (en.MoveNext()); + return StringBuilderCache.GetStringAndRelease(result); + } + } + + // Joins an array of strings together as one string with a string separator between each original string. // [System.Security.SecuritySafeCritical] // auto-generated public unsafe static String Join(String separator, String[] value, int startIndex, int count) { @@ -737,6 +855,74 @@ public unsafe static String Join(String separator, String[] value, int startInde return jointString; } + + // Joins an array of strings together as one string with a char separator between each original string. + // + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static String Join(Char separator, String[] value, int startIndex, int count) { + //Range check the array + if (value == null) + throw new ArgumentNullException("value"); + + if (startIndex < 0) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount")); + + if (startIndex > value.Length - count) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer")); + Contract.EndContractBlock(); + + //If count is 0, that skews a whole bunch of the calculations below, so just special case that. + if (count == 0) { + return String.Empty; + } + + if (count == 1) { + return value[startIndex] ?? String.Empty; + } + + int jointLength = 0; + //Figure out the total length of the strings in value + int endIndex = startIndex + count - 1; + for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++) { + string currentValue = value[stringToJoinIndex]; + + if (currentValue != null) { + jointLength += currentValue.Length; + } + } + + //Add enough room for the separator. + jointLength += count - 1; + + // Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times + // and landed back in the positive range.) The input array might be modifed from other threads, + // so we have to do an overflow check before each append below anyway. Those overflows will get caught down there. + if ((jointLength < 0) || ((jointLength + 1) < 0) ) { + throw new OutOfMemoryException(); + } + + //If this is an empty string, just return. + if (jointLength == 0) { + return String.Empty; + } + + string jointString = FastAllocateString( jointLength ); + fixed (char * pointerToJointString = &jointString.m_firstChar) { + UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength); + + // Append the first string first and then append each following string prefixed by the separator. + charBuffer.AppendString( value[startIndex] ); + for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) { + charBuffer.AppendChar( separator ); + charBuffer.AppendString( value[stringToJoinIndex] ); + } + Contract.Assert(*(pointerToJointString + charBuffer.Length) == '\0', "String must be null-terminated!"); + } + + return jointString; + } // // diff --git a/src/mscorlib/src/System/UnSafeCharBuffer.cs b/src/mscorlib/src/System/UnSafeCharBuffer.cs index 78059b623aef..2be52473ba91 100644 --- a/src/mscorlib/src/System/UnSafeCharBuffer.cs +++ b/src/mscorlib/src/System/UnSafeCharBuffer.cs @@ -29,25 +29,36 @@ public UnSafeCharBuffer( char *buffer, int bufferSize) { m_totalSize = bufferSize; m_length = 0; } - + + [System.Security.SecuritySafeCritical] // auto-generated + public void AppendChar( char charToAppend ) { + if ( (m_totalSize - m_length) < 1 ) { + throw new IndexOutOfRangeException(); + } + + m_buffer[m_length++] = charToAppend; + + Contract.Assert(m_length <= m_totalSize, "Buffer has been overflowed!"); + } + [System.Security.SecuritySafeCritical] // auto-generated public void AppendString(string stringToAppend) { if( String.IsNullOrEmpty( stringToAppend ) ) { return; } - + if ( (m_totalSize - m_length) < stringToAppend.Length ) { throw new IndexOutOfRangeException(); } - - fixed( char* pointerToString = stringToAppend ) { + + fixed( char* pointerToString = stringToAppend ) { Buffer.Memcpy( (byte*) (m_buffer + m_length), (byte *) pointerToString, stringToAppend.Length * sizeof(char)); - } - + } + m_length += stringToAppend.Length; Contract.Assert(m_length <= m_totalSize, "Buffer has been overflowed!"); } - + public int Length { get { return m_length; From 59f0b5977d99c18892bcfd1ee311f676b226a8ff Mon Sep 17 00:00:00 2001 From: Alex Ghiondea Date: Thu, 13 Oct 2016 20:54:58 -0700 Subject: [PATCH 2/2] Address minor code review feedback. --- src/mscorlib/src/System/String.Manipulation.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mscorlib/src/System/String.Manipulation.cs b/src/mscorlib/src/System/String.Manipulation.cs index 98db0fe2e933..68767d10eeee 100644 --- a/src/mscorlib/src/System/String.Manipulation.cs +++ b/src/mscorlib/src/System/String.Manipulation.cs @@ -595,7 +595,7 @@ public static string Join(string separator, params object[] values) return StringBuilderCache.GetStringAndRelease(result); } - // Joins an object array of strings together as one string with a char separator between each original string. + // Joins an array of objects together as one string with a char separator between each original string. // [ComVisible(false)] public static String Join(Char separator, params Object[] values) { @@ -604,6 +604,7 @@ public static String Join(Char separator, params Object[] values) { if (values.Length == 0 || values[0] == null) return String.Empty; + Contract.EndContractBlock(); string firstString = values[0].ToString(); @@ -884,8 +885,8 @@ public unsafe static String Join(Char separator, String[] value, int startIndex, int jointLength = 0; //Figure out the total length of the strings in value - int endIndex = startIndex + count - 1; - for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++) { + int endIndex = startIndex + count; + for (int stringToJoinIndex = startIndex; stringToJoinIndex < endIndex; stringToJoinIndex++) { string currentValue = value[stringToJoinIndex]; if (currentValue != null) { @@ -914,7 +915,7 @@ public unsafe static String Join(Char separator, String[] value, int startIndex, // Append the first string first and then append each following string prefixed by the separator. charBuffer.AppendString( value[startIndex] ); - for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) { + for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex < endIndex; stringToJoinIndex++) { charBuffer.AppendChar( separator ); charBuffer.AppendString( value[stringToJoinIndex] ); }