5
5
using System ;
6
6
using System . Collections . Concurrent ;
7
7
using System . Collections . Generic ;
8
- using System . Linq ;
9
8
using System . Reflection ;
10
9
11
10
namespace Microsoft . AspNetCore . Components
@@ -17,15 +16,16 @@ public static class ParameterCollectionExtensions
17
16
{
18
17
private const BindingFlags _bindablePropertyFlags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance | BindingFlags . IgnoreCase ;
19
18
20
- private readonly static IDictionary < Type , IDictionary < string , IPropertySetter > > _cachedParameterWriters = new ConcurrentDictionary < Type , IDictionary < string , IPropertySetter > > ( ) ;
19
+ private readonly static ConcurrentDictionary < Type , WritersForType > _cachedWritersByType
20
+ = new ConcurrentDictionary < Type , WritersForType > ( ) ;
21
21
22
22
/// <summary>
23
- /// Iterates through the <see cref="ParameterCollection "/>, assigning each parameter
24
- /// to a property of the same name on <paramref name="target "/>.
23
+ /// For each parameter property on <paramref name="target "/>, updates its value to
24
+ /// match the corresponding entry in the <see cref="ParameterCollection "/>.
25
25
/// </summary>
26
26
/// <param name="parameterCollection">The <see cref="ParameterCollection"/>.</param>
27
27
/// <param name="target">An object that has a public writable property matching each parameter's name and type.</param>
28
- public static void SetParameterProperties (
28
+ public unsafe static void SetParameterProperties (
29
29
in this ParameterCollection parameterCollection ,
30
30
object target )
31
31
{
@@ -35,26 +35,36 @@ public static void SetParameterProperties(
35
35
}
36
36
37
37
var targetType = target . GetType ( ) ;
38
- if ( ! _cachedParameterWriters . TryGetValue ( targetType , out var parameterWriters ) )
38
+ if ( ! _cachedWritersByType . TryGetValue ( targetType , out var writers ) )
39
39
{
40
- parameterWriters = CreateParameterWriters ( targetType ) ;
41
- _cachedParameterWriters [ targetType ] = parameterWriters ;
40
+ writers = new WritersForType ( targetType ) ;
41
+ _cachedWritersByType [ targetType ] = writers ;
42
42
}
43
43
44
- var localParameterWriter = parameterWriters . Values . ToList ( ) ;
44
+ // We only want to iterate through the parameterCollection once, and by the end of it,
45
+ // need to have tracked which of the parameter properties haven't yet been written.
46
+ // To avoid allocating any list/dictionary to track that, here we stackalloc an array
47
+ // of flags and set them based on the indices of the writers we use.
48
+ var numWriters = writers . WritersByIndex . Count ;
49
+ var numUsedWriters = 0 ;
50
+
51
+ // TODO: Once we're able to move to netstandard2.1, this can be changed to be
52
+ // a Span<bool> and then the enclosing method no longer needs to be 'unsafe'
53
+ bool * usageFlags = stackalloc bool [ numWriters ] ;
45
54
46
55
foreach ( var parameter in parameterCollection )
47
56
{
48
57
var parameterName = parameter . Name ;
49
- if ( ! parameterWriters . TryGetValue ( parameterName , out var parameterWriter ) )
58
+ if ( ! writers . WritersByName . TryGetValue ( parameterName , out var writerWithIndex ) )
50
59
{
51
60
ThrowForUnknownIncomingParameterName ( targetType , parameterName ) ;
52
61
}
53
62
54
63
try
55
64
{
56
- parameterWriter . SetValue ( target , parameter . Value ) ;
57
- localParameterWriter . Remove ( parameterWriter ) ;
65
+ writerWithIndex . Writer . SetValue ( target , parameter . Value ) ;
66
+ usageFlags [ writerWithIndex . Index ] = true ;
67
+ numUsedWriters ++ ;
58
68
}
59
69
catch ( Exception ex )
60
70
{
@@ -64,44 +74,27 @@ public static void SetParameterProperties(
64
74
}
65
75
}
66
76
67
- foreach ( var nonUsedParameter in localParameterWriter )
68
- {
69
- nonUsedParameter . SetDefaultValue ( target ) ;
70
- }
71
- }
72
-
73
- internal static IEnumerable < PropertyInfo > GetCandidateBindableProperties ( Type targetType )
74
- => MemberAssignment . GetPropertiesIncludingInherited ( targetType , _bindablePropertyFlags ) ;
75
-
76
- private static IDictionary < string , IPropertySetter > CreateParameterWriters ( Type targetType )
77
- {
78
- var result = new Dictionary < string , IPropertySetter > ( StringComparer . OrdinalIgnoreCase ) ;
79
-
80
- foreach ( var propertyInfo in GetCandidateBindableProperties ( targetType ) )
77
+ // Now we can determine whether any writers have not been used, and if there are
78
+ // some unused ones, find them.
79
+ for ( var index = 0 ; numUsedWriters < numWriters ; index ++ )
81
80
{
82
- var shouldCreateWriter = propertyInfo . IsDefined ( typeof ( ParameterAttribute ) )
83
- || propertyInfo . IsDefined ( typeof ( CascadingParameterAttribute ) ) ;
84
- if ( ! shouldCreateWriter )
81
+ if ( index >= numWriters )
85
82
{
86
- continue ;
83
+ // This should not be possible
84
+ throw new InvalidOperationException ( "Ran out of writers before marking them all as used." ) ;
87
85
}
88
86
89
- var propertySetter = MemberAssignment . CreatePropertySetter ( targetType , propertyInfo ) ;
90
-
91
- var propertyName = propertyInfo . Name ;
92
- if ( result . ContainsKey ( propertyName ) )
87
+ if ( ! usageFlags [ index ] )
93
88
{
94
- throw new InvalidOperationException (
95
- $ "The type '{ targetType . FullName } ' declares more than one parameter matching the " +
96
- $ "name '{ propertyName . ToLowerInvariant ( ) } '. Parameter names are case-insensitive and must be unique.") ;
89
+ writers . WritersByIndex [ index ] . SetDefaultValue ( target ) ;
90
+ numUsedWriters ++ ;
97
91
}
98
-
99
- result . Add ( propertyName , propertySetter ) ;
100
92
}
101
-
102
- return result ;
103
93
}
104
94
95
+ internal static IEnumerable < PropertyInfo > GetCandidateBindableProperties ( Type targetType )
96
+ => MemberAssignment . GetPropertiesIncludingInherited ( targetType , _bindablePropertyFlags ) ;
97
+
105
98
private static void ThrowForUnknownIncomingParameterName ( Type targetType , string parameterName )
106
99
{
107
100
// We know we're going to throw by this stage, so it doesn't matter that the following
@@ -129,5 +122,47 @@ private static void ThrowForUnknownIncomingParameterName(Type targetType, string
129
122
$ "matching the name '{ parameterName } '.") ;
130
123
}
131
124
}
125
+
126
+ class WritersForType
127
+ {
128
+ public Dictionary < string , ( int Index , IPropertySetter Writer ) > WritersByName { get ; }
129
+ public List < IPropertySetter > WritersByIndex { get ; }
130
+
131
+ public WritersForType ( Type targetType )
132
+ {
133
+ var propertySettersByName = new Dictionary < string , IPropertySetter > ( StringComparer . OrdinalIgnoreCase ) ;
134
+ foreach ( var propertyInfo in GetCandidateBindableProperties ( targetType ) )
135
+ {
136
+ var shouldCreateWriter = propertyInfo . IsDefined ( typeof ( ParameterAttribute ) )
137
+ || propertyInfo . IsDefined ( typeof ( CascadingParameterAttribute ) ) ;
138
+ if ( ! shouldCreateWriter )
139
+ {
140
+ continue ;
141
+ }
142
+
143
+ var propertySetter = MemberAssignment . CreatePropertySetter ( targetType , propertyInfo ) ;
144
+
145
+ var propertyName = propertyInfo . Name ;
146
+ if ( propertySettersByName . ContainsKey ( propertyName ) )
147
+ {
148
+ throw new InvalidOperationException (
149
+ $ "The type '{ targetType . FullName } ' declares more than one parameter matching the " +
150
+ $ "name '{ propertyName . ToLowerInvariant ( ) } '. Parameter names are case-insensitive and must be unique.") ;
151
+ }
152
+
153
+ propertySettersByName . Add ( propertyName , propertySetter ) ;
154
+ }
155
+
156
+ // Now we know all the entries, construct the resulting list/dictionary
157
+ // with well-defined indices
158
+ WritersByIndex = new List < IPropertySetter > ( ) ;
159
+ WritersByName = new Dictionary < string , ( int , IPropertySetter ) > ( StringComparer . OrdinalIgnoreCase ) ;
160
+ foreach ( var pair in propertySettersByName )
161
+ {
162
+ WritersByName . Add ( pair . Key , ( WritersByIndex . Count , pair . Value ) ) ;
163
+ WritersByIndex . Add ( pair . Value ) ;
164
+ }
165
+ }
166
+ }
132
167
}
133
168
}
0 commit comments