Remove a smattering of string allocations with spans (#32463)
authorStephen Toub <stoub@microsoft.com>
Thu, 20 Feb 2020 02:39:20 +0000 (21:39 -0500)
committerGitHub <noreply@github.com>
Thu, 20 Feb 2020 02:39:20 +0000 (21:39 -0500)
20 files changed:
src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/PropertyTabAttribute.cs
src/libraries/System.Data.Common/src/System/Data/DataTable.cs
src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs
src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs
src/libraries/System.Net.HttpListener/src/System/Net/Managed/ListenerPrefix.cs
src/libraries/System.Net.Mail/src/System/Net/Mime/SmtpDateTime.cs
src/libraries/System.Net.Requests/src/System/Net/FtpControlStream.cs
src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs
src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlObjectSerializerReadContextComplexJson.cs
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/XmlObjectSerializerWriteContextComplexJson.cs
src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
src/libraries/System.Private.Xml/src/System/Xml/Serialization/CodeIdentifier.cs
src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X500NameEncoder.cs
src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs
src/libraries/System.Windows.Extensions/src/System/Drawing/ImageFormatConverter.cs
src/libraries/System.Windows.Extensions/src/System/Xaml/Permissions/XamlAccessLevel.cs

index d051fc6..5285055 100644 (file)
     <Reference Include="System.Drawing.Primitives" />
     <Reference Include="System.IO.FileSystem" />
     <Reference Include="System.Linq" />
+    <Reference Include="System.Memory" />
     <Reference Include="System.Net.Primitives" />
     <Reference Include="System.Net.Security" />
     <Reference Include="System.ObjectModel" />
index 6d0860e..fcad31f 100644 (file)
@@ -89,8 +89,8 @@ namespace System.ComponentModel
 
                         if (commaIndex != -1)
                         {
-                            className = _tabClassNames[i].Substring(0, commaIndex).Trim();
-                            assemblyName = _tabClassNames[i].Substring(commaIndex + 1).Trim();
+                            className = _tabClassNames[i].AsSpan(0, commaIndex).Trim().ToString();
+                            assemblyName = _tabClassNames[i].AsSpan(commaIndex + 1).Trim().ToString();
                         }
                         else
                         {
index 181ec8b..fbab540 100644 (file)
@@ -3702,12 +3702,12 @@ namespace System.Data
                     bool descending = false;
                     if (length >= 5 && string.Compare(current, length - 4, " ASC", 0, 4, StringComparison.OrdinalIgnoreCase) == 0)
                     {
-                        current = current.Substring(0, length - 4).Trim();
+                        current = current.AsSpan(0, length - 4).Trim().ToString();
                     }
                     else if (length >= 6 && string.Compare(current, length - 5, " DESC", 0, 5, StringComparison.OrdinalIgnoreCase) == 0)
                     {
                         descending = true;
-                        current = current.Substring(0, length - 5).Trim();
+                        current = current.AsSpan(0, length - 5).Trim().ToString();
                     }
 
                     // handle brackets.
index d1b7272..c28353d 100644 (file)
@@ -347,7 +347,7 @@ namespace System.Net
                     if (j < 0 || j == i + 1)
                         return null;
 
-                    attrValue = headerValue.Substring(i + 1, j - i - 1).Trim();
+                    attrValue = headerValue.AsSpan(i + 1, j - i - 1).Trim().ToString();
                 }
                 else
                 {
@@ -360,7 +360,7 @@ namespace System.Net
                     if (j == i)
                         return null;
 
-                    attrValue = headerValue.Substring(i, j - i).Trim();
+                    attrValue = headerValue.AsSpan(i, j - i).Trim().ToString();
                 }
 
                 return attrValue;
index 6c228ea..4e4a235 100644 (file)
@@ -251,8 +251,8 @@ namespace System.Net
                 return;
             }
 
-            string name = header.Substring(0, colon).Trim();
-            string val = header.Substring(colon + 1).Trim();
+            string name = header.AsSpan(0, colon).Trim().ToString();
+            string val = header.AsSpan(colon + 1).Trim().ToString();
             if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase))
             {
                 // To match Windows behavior:
index fa8dddd..bfbdf8b 100644 (file)
@@ -112,7 +112,7 @@ namespace System.Net
             {
                 _host = uri.Substring(start_host, colon - start_host);
                 root = uri.IndexOf('/', colon, length - colon);
-                _port = (ushort)int.Parse(uri.Substring(colon + 1, root - colon - 1));
+                _port = (ushort)int.Parse(uri.AsSpan(colon + 1, root - colon - 1));
                 _path = uri.Substring(root);
             }
             else
index bb9a873..c338406 100644 (file)
@@ -200,12 +200,12 @@ namespace System.Net.Mime
 
             // TryParse will parse in base 10 by default.  do not allow any styles of input beyond the default
             // which is numeric values only
-            if (!int.TryParse(offset.Substring(1, 2), NumberStyles.None, CultureInfo.InvariantCulture, out hours))
+            if (!int.TryParse(offset.AsSpan(1, 2), NumberStyles.None, CultureInfo.InvariantCulture, out hours))
             {
                 throw new FormatException(SR.MailDateInvalidFormat);
             }
 
-            if (!int.TryParse(offset.Substring(3, 2), NumberStyles.None, CultureInfo.InvariantCulture, out minutes))
+            if (!int.TryParse(offset.AsSpan(3, 2), NumberStyles.None, CultureInfo.InvariantCulture, out minutes))
             {
                 throw new FormatException(SR.MailDateInvalidFormat);
             }
@@ -273,7 +273,7 @@ namespace System.Net.Mime
             }
 
             // extract the time portion and remove all leading and trailing whitespace
-            string date = data.Substring(0, indexOfTimeZoneSeparator).Trim();
+            string date = data.AsSpan(0, indexOfTimeZoneSeparator).Trim().ToString();
 
             // attempt to parse the DateTime component.
             DateTime dateValue;
@@ -287,7 +287,7 @@ namespace System.Net.Mime
 
             // extract the second half of the string. This will start with at least one whitespace character.
             // Trim the string to remove these characters.
-            string timeZoneString = data.Substring(indexOfTimeZoneSeparator).Trim();
+            string timeZoneString = data.AsSpan(indexOfTimeZoneSeparator).Trim().ToString();
 
             // find, if any, the first whitespace character after the timezone.
             // These will be CFWS and must be ignored. Remove them.
index c5ac697..136e0a6 100644 (file)
@@ -999,7 +999,7 @@ namespace System.Net
                 {
                     pos1++;
                     long result;
-                    if (long.TryParse(str.Substring(pos1, pos2 - pos1),
+                    if (long.TryParse(str.AsSpan(pos1, pos2 - pos1),
                                         NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite,
                                         NumberFormatInfo.InvariantInfo, out result))
                     {
index 57bfee6..7307c18 100644 (file)
@@ -311,9 +311,9 @@ namespace System.Net
                                     //length. since j points to the next ; the operation j -i
                                     //gives the length of the charset
                                     if (j > i)
-                                        _characterSet = contentType.Substring(i, j - i).Trim();
+                                        _characterSet = contentType.AsSpan(i, j - i).Trim().ToString();
                                     else
-                                        _characterSet = contentType.Substring(i).Trim();
+                                        _characterSet = contentType.AsSpan(i).Trim().ToString();
 
                                     //done
                                     break;
index 7358d5d..42b5e1f 100644 (file)
     <Reference Include="System.Collections.NonGeneric" />
     <Reference Include="System.Collections.Specialized" />
     <Reference Include="System.Linq" />
+    <Reference Include="System.Memory" />
     <Reference Include="System.Reflection.Emit.ILGeneration" />
     <Reference Include="System.Reflection.Emit.Lightweight" />
     <Reference Include="System.Reflection.Primitives" />
index e7280da..4fb275b 100644 (file)
@@ -1586,11 +1586,11 @@ namespace System.Runtime.Serialization
                 startIndex = typeName.IndexOf('.', endIndex);
                 if (startIndex < 0)
                 {
-                    nestedParamCounts.Add(int.Parse(typeName.Substring(endIndex + 1), CultureInfo.InvariantCulture));
+                    nestedParamCounts.Add(int.Parse(typeName.AsSpan(endIndex + 1), provider: CultureInfo.InvariantCulture));
                     break;
                 }
                 else
-                    nestedParamCounts.Add(int.Parse(typeName.Substring(endIndex + 1, startIndex - endIndex - 1), CultureInfo.InvariantCulture));
+                    nestedParamCounts.Add(int.Parse(typeName.AsSpan(endIndex + 1, startIndex - endIndex - 1), provider: CultureInfo.InvariantCulture));
             }
             if (localName != null)
                 localName.Append("Of");
@@ -1931,7 +1931,7 @@ namespace System.Runtime.Serialization
                     else
                     {
                         int paramIndex;
-                        if (!int.TryParse(format.Substring(start, i - start), out paramIndex) || paramIndex < 0 || paramIndex >= genericNameProvider.GetParameterCount())
+                        if (!int.TryParse(format.AsSpan(start, i - start), out paramIndex) || paramIndex < 0 || paramIndex >= genericNameProvider.GetParameterCount())
                             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidDataContractException(SR.Format(SR.GenericParameterNotValid, format.Substring(start, i - start), genericNameProvider.GetGenericTypeName(), genericNameProvider.GetParameterCount() - 1)));
                         typeName.Append(genericNameProvider.GetParameterName(paramIndex));
                     }
index 1e91ad1..052b2be 100644 (file)
@@ -165,7 +165,7 @@ namespace System.Runtime.Serialization.Json
                     switch (serverTypeNamespace[0])
                     {
                         case '#':
-                            serverTypeNamespace = string.Concat(Globals.DataContractXsdBaseNamespace, serverTypeNamespace.Substring(1));
+                            serverTypeNamespace = string.Concat(Globals.DataContractXsdBaseNamespace, serverTypeNamespace.AsSpan(1));
                             break;
                         case '\\':
                             if (serverTypeNamespace.Length >= 2)
@@ -198,7 +198,7 @@ namespace System.Runtime.Serialization.Json
                 switch (serverTypeNamespace[0])
                 {
                     case '#':
-                        serverTypeNamespace = string.Concat(Globals.DataContractXsdBaseNamespace, serverTypeNamespace.Substring(1));
+                        serverTypeNamespace = string.Concat(Globals.DataContractXsdBaseNamespace, serverTypeNamespace.AsSpan(1));
                         break;
                     case '\\':
                         if (serverTypeNamespace.Length >= 2)
index b116f64..e3f3f1c 100644 (file)
@@ -109,7 +109,7 @@ namespace System.Runtime.Serialization.Json
                 }
                 else if (dataContractNamespace.StartsWith(Globals.DataContractXsdBaseNamespace, StringComparison.Ordinal))
                 {
-                    return string.Concat("#", dataContractNamespace.Substring(JsonGlobals.DataContractXsdBaseNamespaceLength));
+                    return string.Concat("#", dataContractNamespace.AsSpan(JsonGlobals.DataContractXsdBaseNamespaceLength));
                 }
             }
 
index 98e5a3a..dc9a77d 100644 (file)
@@ -142,10 +142,11 @@ namespace System.Xml
         private char[] _indentChars;
         private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();
 
-        // This method is needed as the native code compiler fails when this initialization is inline
         private static char[] CreateDefaultIndentChars()
         {
-            return new string(DefaultIndentChar, IndentArrayLength).ToCharArray();
+            var result = new char[IndentArrayLength];
+            result.AsSpan().Fill(DefaultIndentChar);
+            return result;
         }
 
         // element stack
index cb23f5b..891d979 100644 (file)
@@ -191,7 +191,7 @@ namespace System.Xml.Serialization
             {
                 EscapeKeywords(name.Substring(0, nameEnd), sb);
                 sb.Append('<');
-                int arguments = int.Parse(name.Substring(nameEnd + 1), CultureInfo.InvariantCulture) + index;
+                int arguments = int.Parse(name.AsSpan(nameEnd + 1), provider: CultureInfo.InvariantCulture) + index;
                 for (; index < arguments; index++)
                 {
                     sb.Append(GetCSharpName(parameters[index]));
index a996e9d..6066e53 100644 (file)
@@ -541,7 +541,7 @@ namespace System.Xml.Serialization
                         }
                         if (numeric)
                         {
-                            long index = long.Parse(name.Substring(prefixLength), NumberStyles.Integer, CultureInfo.InvariantCulture);
+                            long index = long.Parse(name.AsSpan(prefixLength), NumberStyles.Integer, CultureInfo.InvariantCulture);
                             if (index <= int.MaxValue)
                             {
                                 int newIndex = (int)index;
index a409353..84a5978 100644 (file)
@@ -215,7 +215,7 @@ namespace Internal.Cryptography.Pal
             // 7 + 6 = 13, round up to the nearest power-of-two.
             const int InitalRdnSize = 16;
             List<byte[]> encodedSets = new List<byte[]>(InitalRdnSize);
-            char[] chars = stringForm.ToCharArray();
+            ReadOnlySpan<char> chars = stringForm;
 
             int pos;
             int end = chars.Length;
@@ -403,7 +403,7 @@ namespace Internal.Cryptography.Pal
                             Debug.Assert(valueEnd != -1);
                             Debug.Assert(valueStart != -1);
 
-                            encodedSets.Add(ParseRdn(tagOid, chars, valueStart, valueEnd, hadEscapedQuote));
+                            encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote));
                             tagOid = null;
                             valueStart = -1;
                             valueEnd = -1;
@@ -472,7 +472,7 @@ namespace Internal.Cryptography.Pal
                     Debug.Assert(valueStart != -1);
                     Debug.Assert(valueEnd != -1);
 
-                    encodedSets.Add(ParseRdn(tagOid, chars, valueStart, valueEnd, hadEscapedQuote));
+                    encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote));
                     break;
 
                 // If the entire string was empty, or ended in a dnSeparator.
@@ -511,17 +511,11 @@ namespace Internal.Cryptography.Pal
             return new Oid(stringForm.Substring(tagStart, length));
         }
 
-        private static byte[] ParseRdn(Oid tagOid, char[] chars, int valueStart, int valueEnd, bool hadEscapedQuote)
+        private static byte[] ParseRdn(Oid tagOid, ReadOnlySpan<char> chars, bool hadEscapedQuote)
         {
-            ReadOnlySpan<char> charValue;
-
             if (hadEscapedQuote)
             {
-                charValue = ExtractValue(chars, valueStart, valueEnd);
-            }
-            else
-            {
-                charValue = new ReadOnlySpan<char>(chars, valueStart, valueEnd - valueStart);
+                chars = ExtractValue(chars);
             }
 
             using (AsnWriter writer = new AsnWriter(AsnEncodingRules.DER))
@@ -543,20 +537,20 @@ namespace Internal.Cryptography.Pal
                     try
                     {
                         // An email address with an invalid value will throw.
-                        writer.WriteCharacterString(UniversalTagNumber.IA5String, charValue);
+                        writer.WriteCharacterString(UniversalTagNumber.IA5String, chars);
                     }
                     catch (EncoderFallbackException)
                     {
                         throw new CryptographicException(SR.Cryptography_Invalid_IA5String);
                     }
                 }
-                else if (IsValidPrintableString(charValue))
+                else if (IsValidPrintableString(chars))
                 {
-                    writer.WriteCharacterString(UniversalTagNumber.PrintableString, charValue);
+                    writer.WriteCharacterString(UniversalTagNumber.PrintableString, chars);
                 }
                 else
                 {
-                    writer.WriteCharacterString(UniversalTagNumber.UTF8String, charValue);
+                    writer.WriteCharacterString(UniversalTagNumber.UTF8String, chars);
                 }
 
                 writer.PopSequence();
@@ -580,18 +574,13 @@ namespace Internal.Cryptography.Pal
             }
         }
 
-        private static char[] ExtractValue(char[] chars, int valueStart, int valueEnd)
+        private static char[] ExtractValue(ReadOnlySpan<char> chars)
         {
-            // The string is guaranteed to be between ((valueEnd - valueStart) / 2) (all quotes) and
-            // (valueEnd - valueStart - 1) (one escaped quote)
-            List<char> builder = new List<char>(valueEnd - valueStart - 1);
-
+            var builder = new List<char>(chars.Length);
             bool skippedQuote = false;
 
-            for (int i = valueStart; i < valueEnd; i++)
+            foreach (char c in chars)
             {
-                char c = chars[i];
-
                 if (c == '"' && !skippedQuote)
                 {
                     skippedQuote = true;
index 09fdc06..cf6462a 100644 (file)
@@ -55,7 +55,7 @@ namespace System.Text.RegularExpressions
             if (s_includePatternInName)
             {
                 const int DescriptionLimit = 100; // arbitrary limit to avoid very long method names
-                description = "_" + (pattern.Length > DescriptionLimit ? pattern.Substring(0, DescriptionLimit) : pattern);
+                description = string.Concat("_", pattern.Length > DescriptionLimit ? pattern.AsSpan(0, DescriptionLimit) : pattern);
             }
 
             DynamicMethod goMethod = DefineDynamicMethod($"Regex{regexNum}_Go{description}", null, typeof(CompiledRegexRunner));
index c2abf8b..509ba45 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Drawing
                 return base.ConvertFrom(context, culture, value);
             }
 
-            if ((strFormat[0] == '[') && (strFormat.Length >= 50) && Guid.TryParse(strFormat.Substring(14, 36), out Guid guid))
+            if ((strFormat[0] == '[') && (strFormat.Length >= 50) && Guid.TryParse(strFormat.AsSpan(14, 36), out Guid guid))
             {
                 // case #2, this is probably a long format (guid)
                 return new ImageFormat(guid);
index 80b5fef..9987b5f 100644 (file)
@@ -33,8 +33,8 @@ namespace System.Xaml.Permissions
         public static XamlAccessLevel PrivateAccessTo(string assemblyQualifiedTypeName)
         {
             int nameBoundary = assemblyQualifiedTypeName.IndexOf(',');
-            string typeName = assemblyQualifiedTypeName.Substring(0, nameBoundary).Trim();
-            string assemblyFullName = assemblyQualifiedTypeName.Substring(nameBoundary + 1).Trim();
+            string typeName = assemblyQualifiedTypeName.AsSpan(0, nameBoundary).Trim().ToString();
+            string assemblyFullName = assemblyQualifiedTypeName.AsSpan(nameBoundary + 1).Trim().ToString();
             AssemblyName assemblyName = new AssemblyName(assemblyFullName);
             return new XamlAccessLevel(assemblyName.FullName, typeName);
         }