Skip to content

Commit 3c863a5

Browse files
committed
Use System.Linq in fewer places
* Replace with alternatives or iteration where possible * Rewrite Router component to avoid allocations
1 parent 228d8de commit 3c863a5

27 files changed

+572
-233
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
@@ -17,7 +17,13 @@
1717
<argument>ILLink</argument>
1818
<argument>IL2026</argument>
1919
<property name="Scope">member</property>
20-
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.GetRouteableComponents(System.Collections.Generic.IEnumerable{System.Reflection.Assembly})</property>
20+
<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>
21+
</attribute>
22+
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
23+
<argument>ILLink</argument>
24+
<argument>IL2062</argument>
25+
<property name="Scope">member</property>
26+
<property name="Target">M:Microsoft.AspNetCore.Components.RouteTableFactory.Create(System.Collections.Generic.Dictionary{System.Type,System.String[]})</property>
2127
</attribute>
2228
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
2329
<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: 12 additions & 9 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

@@ -160,7 +159,7 @@ static void SetProperty(object target, PropertySetter writer, string parameterNa
160159
}
161160
}
162161

163-
internal static IEnumerable<PropertyInfo> GetCandidateBindableProperties([DynamicallyAccessedMembers(Component)] Type targetType)
162+
internal static MemberAssignment.PropertyEnumerable GetCandidateBindableProperties([DynamicallyAccessedMembers(Component)] Type targetType)
164163
=> MemberAssignment.GetPropertiesIncludingInherited(targetType, _bindablePropertyFlags);
165164

166165
[DoesNotReturn]
@@ -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/Reflection/MemberAssignment.cs

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,136 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Diagnostics.CodeAnalysis;
7-
using System.Linq;
88
using System.Reflection;
9+
using System.Runtime.InteropServices;
910
using static Microsoft.AspNetCore.Internal.LinkerFlags;
1011

1112
namespace Microsoft.AspNetCore.Components.Reflection
1213
{
1314
internal class MemberAssignment
1415
{
15-
public static IEnumerable<PropertyInfo> GetPropertiesIncludingInherited(
16+
public static PropertyEnumerable GetPropertiesIncludingInherited(
1617
[DynamicallyAccessedMembers(Component)] Type type,
1718
BindingFlags bindingFlags)
1819
{
19-
var dictionary = new Dictionary<string, List<PropertyInfo>>();
20+
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
2021

2122
Type? currentType = type;
2223

2324
while (currentType != null)
2425
{
25-
var properties = currentType.GetProperties(bindingFlags | BindingFlags.DeclaredOnly);
26+
var properties = currentType.GetProperties(bindingFlags | BindingFlags.DeclaredOnly);
2627
foreach (var property in properties)
2728
{
2829
if (!dictionary.TryGetValue(property.Name, out var others))
2930
{
30-
others = new List<PropertyInfo>();
31-
dictionary.Add(property.Name, others);
31+
dictionary.Add(property.Name, property);
3232
}
33-
34-
if (others.Any(other => other.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition()))
33+
else if (!IsInheritedProperty(property, others))
3534
{
36-
// This is an inheritance case. We can safely ignore the value of property since
37-
// we have seen a more derived value.
38-
continue;
35+
List<PropertyInfo> many;
36+
if (others is PropertyInfo single)
37+
{
38+
many = new List<PropertyInfo> { single };
39+
dictionary[property.Name] = many;
40+
}
41+
else
42+
{
43+
many = (List<PropertyInfo>)others;
44+
}
45+
many.Add(property);
3946
}
40-
41-
others.Add(property);
4247
}
4348

4449
currentType = currentType.BaseType;
4550
}
4651

47-
return dictionary.Values.SelectMany(p => p);
52+
return new PropertyEnumerable(dictionary);
53+
}
54+
55+
private static bool IsInheritedProperty(PropertyInfo property, object others)
56+
{
57+
if (others is PropertyInfo single)
58+
{
59+
return single.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition();
60+
}
61+
62+
var many = (List<PropertyInfo>)others;
63+
foreach (var other in CollectionsMarshal.AsSpan(many))
64+
{
65+
if (other.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition())
66+
{
67+
return true;
68+
}
69+
}
70+
71+
return false;
72+
}
73+
74+
public ref struct PropertyEnumerable
75+
{
76+
private readonly PropertyEnumerator _enumerator;
77+
78+
public PropertyEnumerable(Dictionary<string, object> dictionary)
79+
{
80+
_enumerator = new PropertyEnumerator(dictionary);
81+
}
82+
83+
public PropertyEnumerator GetEnumerator() => _enumerator;
84+
}
85+
86+
public ref struct PropertyEnumerator
87+
{
88+
// Do NOT make this readonly, or MoveNext will not work
89+
private Dictionary<string, object>.Enumerator _dictionaryEnumerator;
90+
private Span<PropertyInfo>.Enumerator _spanEnumerator;
91+
92+
public PropertyEnumerator(Dictionary<string, object> dictionary)
93+
{
94+
_dictionaryEnumerator = dictionary.GetEnumerator();
95+
_spanEnumerator = Span<PropertyInfo>.Empty.GetEnumerator();
96+
}
97+
98+
public PropertyInfo Current
99+
{
100+
get
101+
{
102+
if (_dictionaryEnumerator.Current.Value is PropertyInfo property)
103+
{
104+
return property;
105+
}
106+
107+
return _spanEnumerator.Current;
108+
}
109+
}
110+
111+
public bool MoveNext()
112+
{
113+
if (_spanEnumerator.MoveNext())
114+
{
115+
return true;
116+
}
117+
118+
if (!_dictionaryEnumerator.MoveNext())
119+
{
120+
return false;
121+
}
122+
123+
var oneOrMoreProperties = _dictionaryEnumerator.Current.Value;
124+
if (oneOrMoreProperties is PropertyInfo)
125+
{
126+
_spanEnumerator = Span<PropertyInfo>.Empty.GetEnumerator();
127+
return true;
128+
}
129+
130+
var many = (List<PropertyInfo>)oneOrMoreProperties;
131+
_spanEnumerator = CollectionsMarshal.AsSpan(many).GetEnumerator();
132+
var moveNext = _spanEnumerator.MoveNext();
133+
Debug.Assert(moveNext, "We expect this to at least have one item.");
134+
return moveNext;
135+
}
48136
}
49137
}
50138
}

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
}

0 commit comments

Comments
 (0)