Skip to content

Commit 1a2b326

Browse files
authored
Further reduce string allocations (#32929)
* Further reduce string allocations
1 parent 7aad26d commit 1a2b326

32 files changed

+528
-255
lines changed

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/NamespaceDirective.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>ASP.NET Core design time hosting infrastructure for the Razor view engine.</Description>
@@ -13,6 +13,7 @@
1313
<Compile Include="..\..\Microsoft.AspNetCore.Razor.Language\src\CodeGeneration\CodeWriterExtensions.cs" Link="Shared\CodeWriterExtensions.cs" />
1414
<Compile Include="..\..\Microsoft.AspNetCore.Razor.Language\src\CSharpIdentifier.cs" Link="Shared\CSharpIdentifier.cs" />
1515
<Compile Include="..\..\Microsoft.AspNetCore.Razor.Language\src\Checksum.cs" Link="Shared\Checksum.cs" />
16+
<Compile Include="..\..\Microsoft.AspNetCore.Razor.Language\src\StringSegment.cs" Link="Shared\Checksum.cs" />
1617
</ItemGroup>
1718

1819
<ItemGroup>

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/MvcViewDocumentClassifierPass.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using Microsoft.AspNetCore.Razor;
56
using Microsoft.AspNetCore.Razor.Language;
67
using Microsoft.AspNetCore.Razor.Language.Intermediate;
78

@@ -46,7 +47,7 @@ protected override void OnDocumentStructureCreated(
4647
// It's possible for a Razor document to not have a file path.
4748
// Eg. When we try to generate code for an in memory document like default imports.
4849
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
49-
@class.ClassName = $"AspNetCore_{checksum}";
50+
@class.ClassName = "AspNetCore_" + checksum;
5051
}
5152
else
5253
{
@@ -94,12 +95,13 @@ private static string GetClassNameFromPath(string path)
9495
return path;
9596
}
9697

98+
var pathSegment = new StringSegment(path);
9799
if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
98100
{
99-
path = path.Substring(0, path.Length - cshtmlExtension.Length);
101+
pathSegment = pathSegment.Subsegment(0, path.Length - cshtmlExtension.Length);
100102
}
101103

102-
return CSharpIdentifier.SanitizeIdentifier(path);
104+
return CSharpIdentifier.SanitizeIdentifier(pathSegment);
103105
}
104106
}
105107
}

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorPageDocumentClassifierPass.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55
using System.Diagnostics;
6+
using Microsoft.AspNetCore.Razor;
67
using Microsoft.AspNetCore.Razor.Language;
78
using Microsoft.AspNetCore.Razor.Language.Extensions;
89
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@@ -187,12 +188,13 @@ private static string GetClassNameFromPath(string path)
187188
return path;
188189
}
189190

191+
var pathSegment = new StringSegment(path);
190192
if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
191193
{
192-
path = path.Substring(0, path.Length - cshtmlExtension.Length);
194+
pathSegment = pathSegment.Subsegment(0, path.Length - cshtmlExtension.Length);
193195
}
194196

195-
return CSharpIdentifier.SanitizeIdentifier(path);
197+
return CSharpIdentifier.SanitizeIdentifier(pathSegment);
196198
}
197199
}
198-
}
200+
}

src/Razor/Microsoft.AspNetCore.Razor.Language/src/CSharpIdentifier.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Globalization;
@@ -33,19 +33,27 @@ private static bool IsIdentifierPartByUnicodeCategory(char character)
3333
category == UnicodeCategory.Format; // Cf
3434
}
3535

36-
public static string SanitizeIdentifier(string inputName)
36+
public static string SanitizeIdentifier(StringSegment inputName)
3737
{
38-
if (string.IsNullOrEmpty(inputName))
38+
if (StringSegment.IsNullOrEmpty(inputName))
3939
{
40-
return inputName;
40+
return string.Empty;
4141
}
4242

43+
var length = inputName.Length;
44+
var prependUnderscore = false;
4345
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
4446
{
45-
inputName = "_" + inputName;
47+
length++;
48+
prependUnderscore = true;
49+
}
50+
51+
var builder = new StringBuilder(length);
52+
if (prependUnderscore)
53+
{
54+
builder.Append('_');
4655
}
4756

48-
var builder = new StringBuilder(inputName.Length);
4957
for (var i = 0; i < inputName.Length; i++)
5058
{
5159
var ch = inputName[i];
@@ -54,5 +62,19 @@ public static string SanitizeIdentifier(string inputName)
5462

5563
return builder.ToString();
5664
}
65+
66+
public static void AppendSanitized(StringBuilder builder, StringSegment inputName)
67+
{
68+
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
69+
{
70+
builder.Append('_');
71+
}
72+
73+
for (var i = 0; i < inputName.Length; i++)
74+
{
75+
var ch = inputName[i];
76+
builder.Append(IsIdentifierPart(ch) ? ch : '_');
77+
}
78+
}
5779
}
5880
}

src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Runtime.CompilerServices;
56
using System.Text;
67

78
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
@@ -114,6 +115,11 @@ public CodeWriter Write(string value)
114115
return Write(value, 0, value.Length);
115116
}
116117

118+
internal CodeWriter Write(StringSegment value)
119+
{
120+
return WriteCore(value.Buffer, value.Offset, value.Length);
121+
}
122+
117123
public CodeWriter Write(string value, int startIndex, int count)
118124
{
119125
if (value == null)
@@ -136,6 +142,12 @@ public CodeWriter Write(string value, int startIndex, int count)
136142
throw new ArgumentOutOfRangeException(nameof(startIndex));
137143
}
138144

145+
return WriteCore(value, startIndex, count);
146+
}
147+
148+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
149+
internal CodeWriter WriteCore(string value, int startIndex, int count)
150+
{
139151
if (count == 0)
140152
{
141153
return this;

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,6 @@ private bool TryComputeAttributeNames(
518518
out BoundAttributeDescriptor changeAttribute,
519519
out BoundAttributeDescriptor expressionAttribute)
520520
{
521-
valueAttributeName = null;
522521
changeAttributeName = null;
523522
expressionAttributeName = null;
524523
changeAttributeNode = null;

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon
660660
context.CodeWriter.Write(".");
661661
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
662662

663-
if (node.TryParseEventCallbackTypeArgument(out var argument))
663+
if (node.TryParseEventCallbackTypeArgument(out StringSegment argument))
664664
{
665665
context.CodeWriter.Write("<");
666666
context.CodeWriter.Write(argument);

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMarkupEncodingPass.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ private bool TryGetHtmlEntity(string content, int position, out string entity, o
177177

178178
// `entity` is guaranteed to be of the format &#****;
179179
var entityValue = entity.Substring(2, entity.Length - 3);
180-
var codePoint = -1;
180+
int codePoint;
181181
if (!int.TryParse(entityValue, out codePoint))
182182
{
183183
// If it is not an integer, check if it is hexadecimal like 0x00CD

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirectivePass.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -67,7 +67,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
6767
routeToken.Content[1] == '/' &&
6868
routeToken.Content[routeToken.Content.Length - 1] == '\"')
6969
{
70-
var template = routeToken.Content.Substring(1, routeToken.Content.Length - 2);
70+
var template = new StringSegment(routeToken.Content, 1, routeToken.Content.Length - 2);
7171
@namespace.Children.Insert(index++, new RouteAttributeExtensionNode(template));
7272
}
7373
else

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon
614614
context.CodeWriter.Write(".");
615615
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
616616

617-
if (node.TryParseEventCallbackTypeArgument(out var argument))
617+
if (node.TryParseEventCallbackTypeArgument(out StringSegment argument))
618618
{
619619
context.CodeWriter.Write("<");
620620
context.CodeWriter.Write(argument);

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/RouteAttributeExtensionNode.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
55
using Microsoft.AspNetCore.Razor.Language.Intermediate;
66

77
namespace Microsoft.AspNetCore.Razor.Language.Components
88
{
9-
internal class RouteAttributeExtensionNode : ExtensionIntermediateNode
9+
internal sealed class RouteAttributeExtensionNode : ExtensionIntermediateNode
1010
{
11-
public RouteAttributeExtensionNode(string template)
11+
public RouteAttributeExtensionNode(StringSegment template)
1212
{
1313
Template = template;
1414
}
1515

16-
public string Template { get; }
16+
public StringSegment Template { get; }
1717

1818
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
1919

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultBoundAttributeDescriptorBuilder.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ private IEnumerable<RazorDiagnostic> Validate()
186186
yield return diagnostic;
187187
}
188188

189-
var name = Name;
189+
StringSegment name = Name;
190190
if (isDirectiveAttribute && name.StartsWith("@", StringComparison.Ordinal))
191191
{
192-
name = name.Substring(1);
192+
name = name.Subsegment(1);
193193
}
194194
else if (isDirectiveAttribute)
195195
{
@@ -201,14 +201,15 @@ private IEnumerable<RazorDiagnostic> Validate()
201201
yield return diagnostic;
202202
}
203203

204-
foreach (var character in name)
204+
for (var i = 0; i < name.Length; i++)
205205
{
206+
var character = name[i];
206207
if (char.IsWhiteSpace(character) || HtmlConventions.InvalidNonWhitespaceHtmlCharacters.Contains(character))
207208
{
208209
var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeName(
209210
_parent.GetDisplayName(),
210211
GetDisplayName(),
211-
name,
212+
name.Value,
212213
character);
213214

214215
yield return diagnostic;
@@ -237,29 +238,30 @@ private IEnumerable<RazorDiagnostic> Validate()
237238
}
238239
else
239240
{
240-
var indexerPrefix = IndexerAttributeNamePrefix;
241+
StringSegment indexerPrefix = IndexerAttributeNamePrefix;
241242
if (isDirectiveAttribute && indexerPrefix.StartsWith("@", StringComparison.Ordinal))
242243
{
243-
indexerPrefix = indexerPrefix.Substring(1);
244+
indexerPrefix = indexerPrefix.Subsegment(1);
244245
}
245246
else if (isDirectiveAttribute)
246247
{
247248
var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundDirectiveAttributePrefix(
248249
_parent.GetDisplayName(),
249250
GetDisplayName(),
250-
indexerPrefix);
251+
indexerPrefix.Value);
251252

252253
yield return diagnostic;
253254
}
254255

255-
foreach (var character in indexerPrefix)
256+
for (var i = 0; i < indexerPrefix.Length; i++)
256257
{
258+
var character = indexerPrefix[i];
257259
if (char.IsWhiteSpace(character) || HtmlConventions.InvalidNonWhitespaceHtmlCharacters.Contains(character))
258260
{
259261
var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributePrefix(
260262
_parent.GetDisplayName(),
261263
GetDisplayName(),
262-
indexerPrefix,
264+
indexerPrefix.Value,
263265
character);
264266

265267
yield return diagnostic;

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,7 +1774,7 @@ public override void VisitMarkupMinimizedTagHelperDirectiveAttribute(MarkupMinim
17741774

17751775
IntermediateNode attributeNode;
17761776
if (parameterMatch &&
1777-
TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter, out _))
1777+
TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter))
17781778
{
17791779
var expectsBooleanValue = associatedAttributeParameterDescriptor.IsBooleanProperty;
17801780
if (!expectsBooleanValue)
@@ -1786,7 +1786,7 @@ public override void VisitMarkupMinimizedTagHelperDirectiveAttribute(MarkupMinim
17861786
attributeNode = new TagHelperDirectiveAttributeParameterIntermediateNode()
17871787
{
17881788
AttributeName = actualAttributeName,
1789-
AttributeNameWithoutParameter = attributeNameWithoutParameter,
1789+
AttributeNameWithoutParameter = attributeNameWithoutParameter.Value,
17901790
OriginalAttributeName = attributeName,
17911791
BoundAttributeParameter = associatedAttributeParameterDescriptor,
17921792
BoundAttribute = associatedAttributeDescriptor,
@@ -1912,12 +1912,12 @@ public override void VisitMarkupTagHelperDirectiveAttribute(MarkupTagHelperDirec
19121912

19131913
IntermediateNode attributeNode;
19141914
if (parameterMatch &&
1915-
TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter, out _))
1915+
TagHelperMatchingConventions.TryGetBoundAttributeParameter(actualAttributeName, out var attributeNameWithoutParameter))
19161916
{
19171917
attributeNode = new TagHelperDirectiveAttributeParameterIntermediateNode()
19181918
{
19191919
AttributeName = actualAttributeName,
1920-
AttributeNameWithoutParameter = attributeNameWithoutParameter,
1920+
AttributeNameWithoutParameter = attributeNameWithoutParameter.Value,
19211921
OriginalAttributeName = attributeName,
19221922
BoundAttributeParameter = associatedAttributeParameterDescriptor,
19231923
BoundAttribute = associatedAttributeDescriptor,

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DefaultTagHelperTargetExtension.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -493,7 +493,7 @@ public void WriteTagHelperRuntime(CodeRenderingContext context, DefaultTagHelper
493493
if (!context.Options.DesignTime)
494494
{
495495
context.CodeWriter.WriteField(FieldUnusedModifiers, PrivateModifiers, "string", StringValueBufferVariableName);
496-
496+
497497
var backedScopeManageVariableName = "__backed" + ScopeManagerVariableName;
498498
context.CodeWriter
499499
.Write("private ")
@@ -650,7 +650,7 @@ internal static string GetDeterministicId(CodeRenderingContext context)
650650

651651
private static string GetPropertyAccessor(DefaultTagHelperPropertyIntermediateNode node)
652652
{
653-
var propertyAccessor = $"{node.FieldName}.{node.PropertyName}";
653+
var propertyAccessor = node.FieldName + "." + node.PropertyName;
654654

655655
if (node.IsIndexerNameMatch)
656656
{

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/PreallocatedAttributeTargetExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;

0 commit comments

Comments
 (0)