@@ -41,7 +41,7 @@ public static void SetProperties(in ParameterView parameters, object target)
41
41
foreach ( var parameter in parameters )
42
42
{
43
43
var parameterName = parameter . Name ;
44
- if ( ! writers . WritersByName . TryGetValue ( parameterName , out var writer ) )
44
+ if ( ! writers . TryGetValue ( parameterName , out var writer ) )
45
45
{
46
46
// Case 1: There is nowhere to put this value.
47
47
ThrowForUnknownIncomingParameterName ( targetType , parameterName ) ;
@@ -82,7 +82,7 @@ public static void SetProperties(in ParameterView parameters, object target)
82
82
isCaptureUnmatchedValuesParameterSetExplicitly = true ;
83
83
}
84
84
85
- if ( writers . WritersByName . TryGetValue ( parameterName , out var writer ) )
85
+ if ( writers . TryGetValue ( parameterName , out var writer ) )
86
86
{
87
87
if ( ! writer . Cascading && parameter . Cascading )
88
88
{
@@ -245,9 +245,15 @@ private static void ThrowForInvalidCaptureUnmatchedValuesParameterType(Type targ
245
245
246
246
private class WritersForType
247
247
{
248
+ private const int MaxCachedWriterLookups = 100 ;
249
+ private readonly Dictionary < string , IPropertySetter > _underlyingWriters ;
250
+ private readonly ConcurrentDictionary < string , IPropertySetter ? > _referenceEqualityWritersCache ;
251
+
248
252
public WritersForType ( Type targetType )
249
253
{
250
- WritersByName = new Dictionary < string , IPropertySetter > ( StringComparer . OrdinalIgnoreCase ) ;
254
+ _underlyingWriters = new Dictionary < string , IPropertySetter > ( StringComparer . OrdinalIgnoreCase ) ;
255
+ _referenceEqualityWritersCache = new ConcurrentDictionary < string , IPropertySetter ? > ( ReferenceEqualityComparer . Instance ) ;
256
+
251
257
foreach ( var propertyInfo in GetCandidateBindableProperties ( targetType ) )
252
258
{
253
259
var parameterAttribute = propertyInfo . GetCustomAttribute < ParameterAttribute > ( ) ;
@@ -267,14 +273,14 @@ public WritersForType(Type targetType)
267
273
268
274
var propertySetter = MemberAssignment . CreatePropertySetter ( targetType , propertyInfo , cascading : cascadingParameterAttribute != null ) ;
269
275
270
- if ( WritersByName . ContainsKey ( propertyName ) )
276
+ if ( _underlyingWriters . ContainsKey ( propertyName ) )
271
277
{
272
278
throw new InvalidOperationException (
273
279
$ "The type '{ targetType . FullName } ' declares more than one parameter matching the " +
274
280
$ "name '{ propertyName . ToLowerInvariant ( ) } '. Parameter names are case-insensitive and must be unique.") ;
275
281
}
276
282
277
- WritersByName . Add ( propertyName , propertySetter ) ;
283
+ _underlyingWriters . Add ( propertyName , propertySetter ) ;
278
284
279
285
if ( parameterAttribute != null && parameterAttribute . CaptureUnmatchedValues )
280
286
{
@@ -298,11 +304,38 @@ public WritersForType(Type targetType)
298
304
}
299
305
}
300
306
301
- public Dictionary < string , IPropertySetter > WritersByName { get ; }
302
-
303
307
public IPropertySetter ? CaptureUnmatchedValuesWriter { get ; }
304
308
305
309
public string ? CaptureUnmatchedValuesPropertyName { get ; }
310
+
311
+ public bool TryGetValue ( string parameterName , [ MaybeNullWhen ( false ) ] out IPropertySetter writer )
312
+ {
313
+ // In intensive parameter-passing scenarios, one of the most expensive things we do is the
314
+ // lookup from parameterName to writer. Pre-5.0 that was because of the string hashing.
315
+ // To optimize this, we now have a cache in front of the lookup which is keyed by parameterName's
316
+ // object identity (not its string hash). So in most cases we can resolve the lookup without
317
+ // having to hash the string. We only fall back on hashing the string if the cache gets full,
318
+ // which would only be in very unusual situations because components don't typically have many
319
+ // parameters, and the parameterName strings usually come from compile-time constants.
320
+ if ( ! _referenceEqualityWritersCache . TryGetValue ( parameterName , out writer ) )
321
+ {
322
+ _underlyingWriters . TryGetValue ( parameterName , out writer ) ;
323
+
324
+ // Note that because we're not locking around this, it's possible we might
325
+ // actually write more than MaxCachedWriterLookups entries due to concurrent
326
+ // writes. However this won't cause any problems.
327
+ // Also note that the value we're caching might be 'null'. It's valid to cache
328
+ // lookup misses just as much as hits, since then we can more quickly identify
329
+ // incoming values that don't have a corresponding writer and thus will end up
330
+ // being passed as catch-all parameter values.
331
+ if ( _referenceEqualityWritersCache . Count < MaxCachedWriterLookups )
332
+ {
333
+ _referenceEqualityWritersCache . TryAdd ( parameterName , writer ) ;
334
+ }
335
+ }
336
+
337
+ return writer != null ;
338
+ }
306
339
}
307
340
}
308
341
}
0 commit comments