Skip to content

Commit 6876339

Browse files
committed
Use System.Linq in fewer places
* Replace with alternatives or iteration where possible * Rewrite Router component to avoid allocations
1 parent 493d0bf commit 6876339

23 files changed

+458
-207
lines changed

src/Components/Authorization/src/AttributeAuthorizeDataCache.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System;
55
using System.Collections.Concurrent;
6-
using System.Linq;
6+
using System.Collections.Generic;
77
using Microsoft.AspNetCore.Authorization;
88

99
namespace Microsoft.AspNetCore.Components.Authorization
@@ -29,13 +29,22 @@ private static IAuthorizeData[] ComputeAuthorizeDataForType(Type type)
2929
{
3030
// Allow Anonymous skips all authorization
3131
var allAttributes = type.GetCustomAttributes(inherit: true);
32-
if (allAttributes.OfType<IAllowAnonymous>().Any())
32+
List<IAuthorizeData> authorizeDatas = null;
33+
for (var i = 0; i < allAttributes.Length; i++)
3334
{
34-
return null;
35+
if (allAttributes[i] is IAllowAnonymous)
36+
{
37+
return null;
38+
}
39+
40+
if (allAttributes[i] is IAuthorizeData authorizeData)
41+
{
42+
authorizeDatas ??= new();
43+
authorizeDatas.Add(authorizeData);
44+
}
3545
}
3646

37-
var authorizeDataAttributes = allAttributes.OfType<IAuthorizeData>().ToArray();
38-
return authorizeDataAttributes.Length > 0 ? authorizeDataAttributes : null;
47+
return authorizeDatas?.ToArray();
3948
}
4049
}
4150
}

src/Components/Components/src/ComponentFactory.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
using System;
55
using System.Collections.Concurrent;
6+
using System.Collections.Generic;
67
using System.Diagnostics.CodeAnalysis;
7-
using System.Linq;
88
using System.Reflection;
99
using Microsoft.AspNetCore.Components.Reflection;
1010
using static Microsoft.AspNetCore.Internal.LinkerFlags;
@@ -56,16 +56,22 @@ private void PerformPropertyInjection(IServiceProvider serviceProvider, ICompone
5656
private Action<IServiceProvider, IComponent> CreateInitializer([DynamicallyAccessedMembers(Component)] Type type)
5757
{
5858
// Do all the reflection up front
59-
var injectableProperties =
60-
MemberAssignment.GetPropertiesIncludingInherited(type, _injectablePropertyBindingFlags)
61-
.Where(p => p.IsDefined(typeof(InjectAttribute)));
59+
List<(string name, Type propertyType, PropertySetter setter)>? injectables = null;
60+
foreach (var property in MemberAssignment.GetPropertiesIncludingInherited(type, _injectablePropertyBindingFlags))
61+
{
62+
if (!property.IsDefined(typeof(InjectAttribute)))
63+
{
64+
continue;
65+
}
6266

63-
var injectables = injectableProperties.Select(property =>
64-
(
65-
propertyName: property.Name,
66-
propertyType: property.PropertyType,
67-
setter: new PropertySetter(type, property)
68-
)).ToArray();
67+
injectables ??= new();
68+
injectables.Add((property.Name, property.PropertyType, new PropertySetter(type, property)));
69+
}
70+
71+
if (injectables is null)
72+
{
73+
return static (_, _) => { };
74+
}
6975

7076
return Initialize;
7177

src/Components/Components/src/Lifetime/ComponentApplicationLifetime.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
7-
using System.Linq;
8-
using System.Runtime.CompilerServices;
97
using System.Threading.Tasks;
108
using Microsoft.AspNetCore.Components.RenderTree;
119
using Microsoft.Extensions.Logging;

src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
<argument>ILLink</argument>
1212
<argument>IL2026</argument>
1313
<property name="Scope">member</property>
14-
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.GetRouteableComponents(System.Collections.Generic.IEnumerable{System.Reflection.Assembly})</property>
14+
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.&lt;GetRouteableComponents&gt;g__GetRouteableComponents|3_0(System.Collections.Generic.List{System.Type},System.Reflection.Assembly)</property>
15+
</attribute>
16+
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
17+
<argument>ILLink</argument>
18+
<argument>IL2062</argument>
19+
<property name="Scope">member</property>
20+
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.Create(System.Collections.Generic.Dictionary{System.Type,System.Collections.Generic.List{System.String}})</property>
1521
</attribute>
1622
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
1723
<argument>ILLink</argument>
@@ -49,12 +55,6 @@
4955
<property name="Scope">member</property>
5056
<property name="Target">M:Microsoft.AspNetCore.Components.Reflection.ComponentProperties.SetProperties(Microsoft.AspNetCore.Components.ParameterView@,System.Object)</property>
5157
</attribute>
52-
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
53-
<argument>ILLink</argument>
54-
<argument>IL2072</argument>
55-
<property name="Scope">member</property>
56-
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.Create(System.Collections.Generic.Dictionary{System.Type,System.String[]})</property>
57-
</attribute>
5858
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
5959
<argument>ILLink</argument>
6060
<argument>IL2077</argument>

src/Components/Components/src/Reflection/ComponentProperties.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
77
using System.Diagnostics.CodeAnalysis;
8-
using System.Linq;
98
using System.Reflection;
109
using static Microsoft.AspNetCore.Internal.LinkerFlags;
1110

@@ -215,19 +214,23 @@ private static void ThrowForCaptureUnmatchedValuesConflict(Type targetType, stri
215214
throw new InvalidOperationException(
216215
$"The property '{parameterName}' on component type '{targetType.FullName}' cannot be set explicitly " +
217216
$"when also used to capture unmatched values. Unmatched values:" + Environment.NewLine +
218-
string.Join(Environment.NewLine, unmatched.Keys.OrderBy(k => k)));
217+
string.Join(Environment.NewLine, unmatched.Keys));
219218
}
220219

221220
[DoesNotReturn]
222221
private static void ThrowForMultipleCaptureUnmatchedValuesParameters([DynamicallyAccessedMembers(Component)] Type targetType)
223222
{
224223
// We don't care about perf here, we want to report an accurate and useful error.
225-
var propertyNames = targetType
226-
.GetProperties(_bindablePropertyFlags)
227-
.Where(p => p.GetCustomAttribute<ParameterAttribute>()?.CaptureUnmatchedValues == true)
228-
.Select(p => p.Name)
229-
.OrderBy(p => p)
230-
.ToArray();
224+
var propertyNames = new List<string>();
225+
foreach (var property in targetType.GetProperties(_bindablePropertyFlags))
226+
{
227+
if (property.GetCustomAttribute<ParameterAttribute>()?.CaptureUnmatchedValues == true)
228+
{
229+
propertyNames.Add(property.Name);
230+
}
231+
}
232+
233+
propertyNames.Sort(StringComparer.Ordinal);
231234

232235
throw new InvalidOperationException(
233236
$"Multiple properties were found on component type '{targetType.FullName}' with " +

src/Components/Components/src/Routing/RouteEntry.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Components.Routing
1414
[DebuggerDisplay("Handler = {Handler}, Template = {Template}")]
1515
internal class RouteEntry
1616
{
17-
public RouteEntry(RouteTemplate template, [DynamicallyAccessedMembers(Component)] Type handler, string[] unusedRouteParameterNames)
17+
public RouteEntry(RouteTemplate template, [DynamicallyAccessedMembers(Component)] Type handler, List<string>? unusedRouteParameterNames)
1818
{
1919
Template = template;
2020
UnusedRouteParameterNames = unusedRouteParameterNames;
@@ -23,7 +23,7 @@ public RouteEntry(RouteTemplate template, [DynamicallyAccessedMembers(Component)
2323

2424
public RouteTemplate Template { get; }
2525

26-
public string[] UnusedRouteParameterNames { get; }
26+
public List<string>? UnusedRouteParameterNames { get; }
2727

2828
[DynamicallyAccessedMembers(Component)]
2929
public Type Handler { get; }
@@ -113,10 +113,10 @@ internal void Match(RouteContext context)
113113
parameters ??= new Dictionary<string, object>(StringComparer.Ordinal);
114114
AddDefaultValues(parameters, templateIndex, Template.Segments);
115115
}
116-
if (UnusedRouteParameterNames?.Length > 0)
116+
if (UnusedRouteParameterNames?.Count > 0)
117117
{
118118
parameters ??= new Dictionary<string, object>(StringComparer.Ordinal);
119-
for (var i = 0; i < UnusedRouteParameterNames.Length; i++)
119+
for (var i = 0; i < UnusedRouteParameterNames.Count; i++)
120120
{
121121
parameters[UnusedRouteParameterNames[i]] = null;
122122
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
8+
namespace Microsoft.AspNetCore.Components.Routing
9+
{
10+
internal readonly struct RouteKey : IEquatable<RouteKey>
11+
{
12+
public readonly Assembly? AppAssembly;
13+
public readonly HashSet<Assembly>? AdditionalAssemblies;
14+
15+
public RouteKey(Assembly appAssembly, IEnumerable<Assembly> additionalAssemblies)
16+
{
17+
AppAssembly = appAssembly;
18+
AdditionalAssemblies = additionalAssemblies is null ? null : new HashSet<Assembly>(additionalAssemblies);
19+
}
20+
21+
public override bool Equals(object? obj)
22+
{
23+
return obj is RouteKey other && Equals(other);
24+
}
25+
26+
public bool Equals(RouteKey other)
27+
{
28+
if (!Equals(AppAssembly, other.AppAssembly))
29+
{
30+
return false;
31+
}
32+
33+
if (AdditionalAssemblies is null && other.AdditionalAssemblies is null)
34+
{
35+
return true;
36+
}
37+
38+
if (AdditionalAssemblies is null || other.AdditionalAssemblies is null)
39+
{
40+
return false;
41+
}
42+
43+
return AdditionalAssemblies.Count == other.AdditionalAssemblies.Count &&
44+
AdditionalAssemblies.SetEquals(other.AdditionalAssemblies);
45+
}
46+
47+
public override int GetHashCode()
48+
{
49+
if (AppAssembly is null)
50+
{
51+
return 0;
52+
}
53+
54+
if (AdditionalAssemblies is null)
55+
{
56+
return AppAssembly.GetHashCode();
57+
}
58+
59+
var hash = new HashCode();
60+
hash.Add(AppAssembly);
61+
// Producing a hash code that includes individual assemblies requires it to have a stable order.
62+
// We'll avoid the cost of sorting and simply include the number of assemblies instead.
63+
hash.Add(AdditionalAssemblies.Count);
64+
65+
return hash.ToHashCode();
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)