@@ -16,18 +16,16 @@ public static class ParameterCollectionExtensions
16
16
{
17
17
private const BindingFlags _bindablePropertyFlags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance | BindingFlags . IgnoreCase ;
18
18
19
- private delegate void WriteParameterAction ( object target , object parameterValue ) ;
20
-
21
- private readonly static IDictionary < Type , IDictionary < string , WriteParameterAction > > _cachedParameterWriters
22
- = new ConcurrentDictionary < Type , IDictionary < string , WriteParameterAction > > ( ) ;
19
+ private readonly static ConcurrentDictionary < Type , WritersForType > _cachedWritersByType
20
+ = new ConcurrentDictionary < Type , WritersForType > ( ) ;
23
21
24
22
/// <summary>
25
- /// Iterates through the <see cref="ParameterCollection "/>, assigning each parameter
26
- /// 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 "/>.
27
25
/// </summary>
28
26
/// <param name="parameterCollection">The <see cref="ParameterCollection"/>.</param>
29
27
/// <param name="target">An object that has a public writable property matching each parameter's name and type.</param>
30
- public static void AssignToProperties (
28
+ public unsafe static void SetParameterProperties (
31
29
in this ParameterCollection parameterCollection ,
32
30
object target )
33
31
{
@@ -37,23 +35,36 @@ public static void AssignToProperties(
37
35
}
38
36
39
37
var targetType = target . GetType ( ) ;
40
- if ( ! _cachedParameterWriters . TryGetValue ( targetType , out var parameterWriters ) )
38
+ if ( ! _cachedWritersByType . TryGetValue ( targetType , out var writers ) )
41
39
{
42
- parameterWriters = CreateParameterWriters ( targetType ) ;
43
- _cachedParameterWriters [ targetType ] = parameterWriters ;
40
+ writers = new WritersForType ( targetType ) ;
41
+ _cachedWritersByType [ targetType ] = writers ;
44
42
}
45
43
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 ] ;
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 ( target , parameter . Value ) ;
65
+ writerWithIndex . Writer . SetValue ( target , parameter . Value ) ;
66
+ usageFlags [ writerWithIndex . Index ] = true ;
67
+ numUsedWriters ++ ;
57
68
}
58
69
catch ( Exception ex )
59
70
{
@@ -62,43 +73,28 @@ public static void AssignToProperties(
62
73
$ "type '{ target . GetType ( ) . FullName } '. The error was: { ex . Message } ", ex ) ;
63
74
}
64
75
}
65
- }
66
-
67
- internal static IEnumerable < PropertyInfo > GetCandidateBindableProperties ( Type targetType )
68
- => MemberAssignment . GetPropertiesIncludingInherited ( targetType , _bindablePropertyFlags ) ;
69
76
70
- private static IDictionary < string , WriteParameterAction > CreateParameterWriters ( Type targetType )
71
- {
72
- var result = new Dictionary < string , WriteParameterAction > ( StringComparer . OrdinalIgnoreCase ) ;
73
-
74
- 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 ++ )
75
80
{
76
- var shouldCreateWriter = propertyInfo . IsDefined ( typeof ( ParameterAttribute ) )
77
- || propertyInfo . IsDefined ( typeof ( CascadingParameterAttribute ) ) ;
78
- if ( ! shouldCreateWriter )
81
+ if ( index >= numWriters )
79
82
{
80
- continue ;
83
+ // This should not be possible
84
+ throw new InvalidOperationException ( "Ran out of writers before marking them all as used." ) ;
81
85
}
82
86
83
- var propertySetter = MemberAssignment . CreatePropertySetter ( targetType , propertyInfo ) ;
84
-
85
- var propertyName = propertyInfo . Name ;
86
- if ( result . ContainsKey ( propertyName ) )
87
+ if ( ! usageFlags [ index ] )
87
88
{
88
- throw new InvalidOperationException (
89
- $ "The type '{ targetType . FullName } ' declares more than one parameter matching the " +
90
- $ "name '{ propertyName . ToLowerInvariant ( ) } '. Parameter names are case-insensitive and must be unique.") ;
89
+ writers . WritersByIndex [ index ] . SetDefaultValue ( target ) ;
90
+ numUsedWriters ++ ;
91
91
}
92
-
93
- result . Add ( propertyName , ( object target , object parameterValue ) =>
94
- {
95
- propertySetter . SetValue ( target , parameterValue ) ;
96
- } ) ;
97
92
}
98
-
99
- return result ;
100
93
}
101
94
95
+ internal static IEnumerable < PropertyInfo > GetCandidateBindableProperties ( Type targetType )
96
+ => MemberAssignment . GetPropertiesIncludingInherited ( targetType , _bindablePropertyFlags ) ;
97
+
102
98
private static void ThrowForUnknownIncomingParameterName ( Type targetType , string parameterName )
103
99
{
104
100
// We know we're going to throw by this stage, so it doesn't matter that the following
@@ -126,5 +122,47 @@ private static void ThrowForUnknownIncomingParameterName(Type targetType, string
126
122
$ "matching the name '{ parameterName } '.") ;
127
123
}
128
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
+ }
129
167
}
130
168
}
0 commit comments