Avoid unnecessary allocations in Exception.ToString
authorStephen Toub <stoub@microsoft.com>
Sat, 26 Oct 2019 00:57:13 +0000 (20:57 -0400)
committerStephen Toub <stoub@microsoft.com>
Sat, 26 Oct 2019 10:47:52 +0000 (06:47 -0400)
Commit migrated from https://github.com/dotnet/coreclr/commit/373a7b2ea2b6067a6a12775dd54a9e20dd0190d1

src/libraries/System.Private.CoreLib/src/System/Exception.cs

index a437da1..2e46fbd 100644 (file)
@@ -120,26 +120,65 @@ namespace System
 
         public override string ToString()
         {
-            string s = GetClassName();
-
+            string className = GetClassName();
             string? message = Message;
-            if (!string.IsNullOrEmpty(message))
+            string innerExceptionString = _innerException?.ToString() ?? "";
+            string endOfInnerExceptionResource = SR.Exception_EndOfInnerExceptionStack;
+            string? stackTrace = StackTrace;
+
+            // Calculate result string length
+            int length = className.Length;
+            checked
             {
-                s += ": " + message;
+                if (!string.IsNullOrEmpty(message))
+                {
+                    length += 2 + message.Length;
+                }
+                if (_innerException != null)
+                {
+                    length += Environment.NewLineConst.Length + InnerExceptionPrefix.Length + innerExceptionString.Length + Environment.NewLineConst.Length + 3 + endOfInnerExceptionResource.Length;
+                }
+                if (stackTrace != null)
+                {
+                    length += Environment.NewLineConst.Length + stackTrace.Length;
+                }
             }
 
+            // Create the string
+            string result = string.FastAllocateString(length);
+            Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
+
+            // Fill it in
+            Write(className, ref resultSpan);
+            if (!string.IsNullOrEmpty(message))
+            {
+                Write(": ", ref resultSpan);
+                Write(message, ref resultSpan);
+            }
             if (_innerException != null)
             {
-                s += Environment.NewLineConst + InnerExceptionPrefix + _innerException.ToString() + Environment.NewLineConst + "   " + SR.Exception_EndOfInnerExceptionStack;
+                Write(Environment.NewLineConst, ref resultSpan);
+                Write(InnerExceptionPrefix, ref resultSpan);
+                Write(innerExceptionString, ref resultSpan);
+                Write(Environment.NewLineConst, ref resultSpan);
+                Write("   ", ref resultSpan);
+                Write(endOfInnerExceptionResource, ref resultSpan);
             }
-
-            string? stackTrace = StackTrace;
             if (stackTrace != null)
             {
-                s += Environment.NewLineConst + stackTrace;
+                Write(Environment.NewLineConst, ref resultSpan);
+                Write(stackTrace, ref resultSpan);
             }
+            Debug.Assert(resultSpan.Length == 0);
 
-            return s;
+            // Return it
+            return result;
+
+            static void Write(string source, ref Span<char> dest)
+            {
+                source.AsSpan().CopyTo(dest);
+                dest = dest.Slice(source.Length);
+            }
         }
 
         protected event EventHandler<SafeSerializationEventArgs>? SerializeObjectState