@@ -12,6 +12,12 @@ internal sealed class GenericRegistrationEntry : IRegistrationEntry
1212 private readonly List < IProducerProvider > providers = new ( ) ;
1313 private readonly ContainerOptions options ;
1414
15+ // PERF: #985 These two collections exist solely for performance optimizations. Registering many
16+ // closed-generic types of the same generic abstractions got exponentially slower with the number
17+ // of registrations. These two collections help optimize this.
18+ private Dictionary < Type , ClosedToInstanceProducerProviderDictionaryEntry > ? closedProviders ;
19+ private List < OpenGenericToInstanceProducerProvider > ? openProviders ;
20+
1521 internal GenericRegistrationEntry ( Container container )
1622 {
1723 this . options = container . Options ;
@@ -49,19 +55,55 @@ public void Add(InstanceProducer producer)
4955 {
5056 this . Container . ThrowWhenContainerIsLockedOrDisposed ( ) ;
5157
52- this . ThrowWhenConditionalIsRegisteredInOverridingMode ( producer ) ;
58+ Type serviceType = producer . ServiceType ;
5359
5460 if ( ! this . AllowOverridingRegistrations )
5561 {
56- this . ThrowWhenOverlappingRegistrationsExist ( producer ) ; // O(n) operation
62+ this . ThrowWhenOverlappingRegistrationsExist ( producer ) ;
5763 }
58-
59- if ( this . AllowOverridingRegistrations )
64+ else
6065 {
61- this . providers . RemoveAll ( p => p . ServiceType == producer . ServiceType ) ; // O(n) operation
66+ this . ThrowWhenConditionalIsRegisteredInOverridingMode ( producer ) ;
67+
68+ if ( this . closedProviders != null )
69+ {
70+ if ( this . closedProviders . ContainsKey ( serviceType ) )
71+ {
72+ this . closedProviders . Remove ( serviceType ) ;
73+ this . providers . RemoveAll ( p => p . ServiceType == serviceType ) ;
74+ }
75+ }
6276 }
6377
64- this . providers . Add ( new ClosedToInstanceProducerProvider ( producer ) ) ;
78+ var provider = new ClosedToInstanceProducerProvider ( producer ) ;
79+
80+ this . AddClosedToInstanceProducerProvider ( provider ) ;
81+ }
82+
83+ private void AddClosedToInstanceProducerProvider ( ClosedToInstanceProducerProvider provider )
84+ {
85+ Type serviceType = provider . ServiceType ;
86+
87+ this . providers . Add ( provider ) ;
88+
89+ if ( this . closedProviders is null )
90+ {
91+ this . closedProviders = new Dictionary < Type , ClosedToInstanceProducerProviderDictionaryEntry >
92+ {
93+ [ serviceType ] = new ClosedToInstanceProducerProviderDictionaryEntry ( provider )
94+ } ;
95+ }
96+ else
97+ {
98+ if ( this . closedProviders . TryGetValue ( serviceType , out var entry ) )
99+ {
100+ entry . Add ( provider ) ;
101+ }
102+ else
103+ {
104+ this . closedProviders [ serviceType ] = new ClosedToInstanceProducerProviderDictionaryEntry ( provider ) ;
105+ }
106+ }
65107 }
66108
67109 public void AddGeneric (
@@ -84,10 +126,12 @@ public void AddGeneric(
84126 if ( provider . GetAppliesToAllClosedServiceTypes ( ) )
85127 {
86128 this . providers . RemoveAll ( p => p . GetAppliesToAllClosedServiceTypes ( ) ) ;
129+
130+ this . openProviders ? . RemoveAll ( p => p . GetAppliesToAllClosedServiceTypes ( ) ) ;
87131 }
88132 }
89133
90- this . providers . Add ( provider ) ;
134+ this . AddOpenGenericToInstanceProducerProvider ( provider ) ;
91135 }
92136
93137 public void Add (
@@ -105,7 +149,19 @@ public void Add(
105149
106150 this . ThrowWhenProviderToRegisterOverlapsWithExistingProvider ( provider ) ;
107151
152+ this . AddOpenGenericToInstanceProducerProvider ( provider ) ;
153+ }
154+
155+ private void AddOpenGenericToInstanceProducerProvider ( OpenGenericToInstanceProducerProvider provider )
156+ {
108157 this . providers . Add ( provider ) ;
158+
159+ if ( this . openProviders is null )
160+ {
161+ this . openProviders = new List < OpenGenericToInstanceProducerProvider > ( ) ;
162+ }
163+
164+ this . openProviders . Add ( provider ) ;
109165 }
110166
111167 public InstanceProducer ? TryGetInstanceProducer (
@@ -172,14 +228,49 @@ private void ThrowWhenOverlappingRegistrationsExist(InstanceProducer producerToR
172228
173229 private IProducerProvider ? GetFirstOverlappingProvider ( InstanceProducer producerToRegister )
174230 {
231+ ClosedToInstanceProducerProvider ? firstOverlappingClosedProvider = null ;
232+ OpenGenericToInstanceProducerProvider ? firstOverlappingOpenProvider = null ;
233+
234+ if ( this . closedProviders != null )
235+ {
236+ // PERF: Only closed providers exist. We can speed up the operation by going just through
237+ // the closed providers for the given service type.
238+ if ( this . closedProviders . TryGetValue ( producerToRegister . ServiceType , out var entry ) )
239+ {
240+ firstOverlappingClosedProvider = entry . OverlapsWith ( producerToRegister ) ;
241+ }
242+ }
243+
244+ if ( this . openProviders != null )
245+ {
246+ foreach ( var openProvider in this . openProviders )
247+ {
248+ if ( openProvider . OverlapsWith ( producerToRegister ) )
249+ {
250+ firstOverlappingOpenProvider = openProvider ;
251+ break ;
252+ }
253+ }
254+ }
255+
256+ // PERF: Is most cases we can prevent going through the list when there's only one of the two
257+ // overlapping.
258+ if ( firstOverlappingClosedProvider is null && firstOverlappingOpenProvider is null ) return null ;
259+ if ( firstOverlappingClosedProvider != null ) return firstOverlappingClosedProvider ;
260+ if ( firstOverlappingOpenProvider != null ) return firstOverlappingOpenProvider ;
261+
262+ // To bad, there is both an overlapping open and a closed provider. Since we must report the first
263+ // first overlapping provider, we have to go through the list (again).
175264 foreach ( var provider in this . providers )
176265 {
177- if ( provider . OverlapsWith ( producerToRegister ) )
266+ if ( provider == firstOverlappingClosedProvider ||
267+ provider == firstOverlappingOpenProvider )
178268 {
179269 return provider ;
180270 }
181271 }
182272
273+ // Will never come here, but the C# compiler doesn't know.
183274 return null ;
184275 }
185276
@@ -541,5 +632,55 @@ private bool IsImplementationApplicableToEveryGenericType(Type implementationTyp
541632 this . ServiceType ,
542633 implementationType ) ;
543634 }
635+
636+ private sealed class ClosedToInstanceProducerProviderDictionaryEntry
637+ {
638+ private readonly ClosedToInstanceProducerProvider firstProvider ;
639+
640+ private List < ClosedToInstanceProducerProvider > ? providers ;
641+
642+ public ClosedToInstanceProducerProviderDictionaryEntry ( ClosedToInstanceProducerProvider firstProvider )
643+ {
644+ this . firstProvider = firstProvider ;
645+ }
646+
647+ public int Count => this . providers ? . Count ?? 1 ;
648+
649+ internal void Add ( ClosedToInstanceProducerProvider provider )
650+ {
651+ if ( this . providers is null )
652+ {
653+ this . providers = new List < ClosedToInstanceProducerProvider >
654+ {
655+ this . firstProvider ,
656+ provider
657+ } ;
658+ }
659+ else
660+ {
661+ this . providers . Add ( provider ) ;
662+ }
663+ }
664+
665+ internal ClosedToInstanceProducerProvider ? OverlapsWith ( InstanceProducer producerToRegister )
666+ {
667+ if ( this . providers is null )
668+ {
669+ return this . firstProvider . OverlapsWith ( producerToRegister ) ? this . firstProvider : null ;
670+ }
671+ else
672+ {
673+ foreach ( var provider in this . providers )
674+ {
675+ if ( provider . OverlapsWith ( producerToRegister ) )
676+ {
677+ return provider ;
678+ }
679+ }
680+
681+ return null ;
682+ }
683+ }
684+ }
544685 }
545686}
0 commit comments