@@ -206,7 +206,8 @@ public void CanUseBuilderPropertyBag()
206206 Assert . Equal ( "value" , builder . Properties [ "prop" ] ) ;
207207 }
208208
209- // A module that overrides neither hook (the common case that caused the performance issue).
209+ // #1446: A module that overrides neither hook (the common case that caused the
210+ // O(N^2) performance issue) must not subscribe to the registry events at all.
210211 internal class NoHookModule : Module
211212 {
212213 protected override void Load ( ContainerBuilder builder )
@@ -216,38 +217,35 @@ protected override void Load(ContainerBuilder builder)
216217 }
217218
218219 [ Fact ]
219- public void NoHookModule_DoesNotSubscribeToRegisteredEvent ( )
220+ public void ModuleWithoutHookDoesNotSubscribeToRegisteredEvent ( )
220221 {
221- // Arrange
222+ // #1446: Subscribing replays every existing registration, so a module that
223+ // does not override AttachToComponentRegistration must not subscribe.
222224 using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
223225
224- // Act
225226 new NoHookModule ( ) . Configure ( registryBuilder ) ;
226227
227- // Assert: the Registered event delegate stored in Properties should be null
228- // (no module subscription was added), meaning the key is either absent or null.
229228 var hasRegisteredHandler =
230229 registryBuilder . Properties . TryGetValue ( MetadataKeys . RegisteredPropertyKey , out var registeredValue )
231230 && registeredValue is not null ;
232231
233- Assert . False ( hasRegisteredHandler , "A module that does not override AttachToComponentRegistration should not subscribe to the Registered event." ) ;
232+ Assert . False ( hasRegisteredHandler ) ;
234233 }
235234
236235 [ Fact ]
237- public void NoHookModule_DoesNotSubscribeToRegistrationSourceAddedEvent ( )
236+ public void ModuleWithoutHookDoesNotSubscribeToRegistrationSourceAddedEvent ( )
238237 {
239- // Arrange
238+ // #1446: A module that does not override AttachToRegistrationSource must not
239+ // subscribe to the RegistrationSourceAdded event.
240240 using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
241241
242- // Act
243242 new NoHookModule ( ) . Configure ( registryBuilder ) ;
244243
245- // Assert: the RegistrationSourceAdded event delegate stored in Properties should be null.
246244 var hasSourceHandler =
247245 registryBuilder . Properties . TryGetValue ( MetadataKeys . RegistrationSourceAddedPropertyKey , out var sourceValue )
248246 && sourceValue is not null ;
249247
250- Assert . False ( hasSourceHandler , "A module that does not override AttachToRegistrationSource should not subscribe to the RegistrationSourceAdded event." ) ;
248+ Assert . False ( hasSourceHandler ) ;
251249 }
252250
253251 // A module that overrides only AttachToComponentRegistration.
@@ -264,26 +262,24 @@ protected override void AttachToComponentRegistration(
264262 }
265263
266264 [ Fact ]
267- public void ComponentRegistrationHookModule_SubscribesToRegisteredEvent ( )
265+ public void ModuleOverridingComponentRegistrationHookSubscribesToRegisteredEvent ( )
268266 {
269- // Arrange
267+ // #1446: A module that overrides AttachToComponentRegistration must still subscribe.
270268 using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
271269
272- // Act
273270 new ComponentRegistrationHookModule ( ) . Configure ( registryBuilder ) ;
274271
275- // Assert: the Registered event delegate stored in Properties should be non-null.
276272 var hasRegisteredHandler =
277273 registryBuilder . Properties . TryGetValue ( MetadataKeys . RegisteredPropertyKey , out var registeredValue )
278274 && registeredValue is not null ;
279275
280- Assert . True ( hasRegisteredHandler , "A module that overrides AttachToComponentRegistration must subscribe to the Registered event." ) ;
276+ Assert . True ( hasRegisteredHandler ) ;
281277 }
282278
283279 [ Fact ]
284- public void ComponentRegistrationHookModule_HookIsCalledForSubsequentRegistrations ( )
280+ public void ModuleOverridingComponentRegistrationHookStillReceivesRegistrations ( )
285281 {
286- // Verify the hook is still called when the override is present (regression guard) .
282+ // #1446: regression guard - the overridden hook must still fire for registrations .
287283 var module = new ComponentRegistrationHookModule ( ) ;
288284 var builder = new ContainerBuilder ( ) ;
289285 builder . RegisterModule ( module ) ;
@@ -294,6 +290,22 @@ public void ComponentRegistrationHookModule_HookIsCalledForSubsequentRegistratio
294290 Assert . NotEmpty ( module . Seen ) ;
295291 }
296292
293+ [ Fact ]
294+ public void ModuleOverridingOnlyComponentRegistrationHookDoesNotSubscribeToRegistrationSourceAddedEvent ( )
295+ {
296+ // #1446: the two detection paths are independent - overriding only the
297+ // component-registration hook must not subscribe to the source-added event.
298+ using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
299+
300+ new ComponentRegistrationHookModule ( ) . Configure ( registryBuilder ) ;
301+
302+ var hasSourceHandler =
303+ registryBuilder . Properties . TryGetValue ( MetadataKeys . RegistrationSourceAddedPropertyKey , out var sourceValue )
304+ && sourceValue is not null ;
305+
306+ Assert . False ( hasSourceHandler ) ;
307+ }
308+
297309 // A module that overrides only AttachToRegistrationSource.
298310 internal class RegistrationSourceHookModule : Module
299311 {
@@ -308,20 +320,34 @@ protected override void AttachToRegistrationSource(
308320 }
309321
310322 [ Fact ]
311- public void RegistrationSourceHookModule_SubscribesToRegistrationSourceAddedEvent ( )
323+ public void ModuleOverridingRegistrationSourceHookSubscribesToRegistrationSourceAddedEvent ( )
312324 {
313- // Arrange
325+ // #1446: A module that overrides AttachToRegistrationSource must still subscribe.
314326 using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
315327
316- // Act
317328 new RegistrationSourceHookModule ( ) . Configure ( registryBuilder ) ;
318329
319- // Assert: the RegistrationSourceAdded event delegate stored in Properties should be non-null.
320330 var hasSourceHandler =
321331 registryBuilder . Properties . TryGetValue ( MetadataKeys . RegistrationSourceAddedPropertyKey , out var sourceValue )
322332 && sourceValue is not null ;
323333
324- Assert . True ( hasSourceHandler , "A module that overrides AttachToRegistrationSource must subscribe to the RegistrationSourceAdded event." ) ;
334+ Assert . True ( hasSourceHandler ) ;
335+ }
336+
337+ [ Fact ]
338+ public void ModuleOverridingOnlyRegistrationSourceHookDoesNotSubscribeToRegisteredEvent ( )
339+ {
340+ // #1446: the two detection paths are independent - overriding only the
341+ // source-added hook must not subscribe to the component-registered event.
342+ using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
343+
344+ new RegistrationSourceHookModule ( ) . Configure ( registryBuilder ) ;
345+
346+ var hasRegisteredHandler =
347+ registryBuilder . Properties . TryGetValue ( MetadataKeys . RegisteredPropertyKey , out var registeredValue )
348+ && registeredValue is not null ;
349+
350+ Assert . False ( hasRegisteredHandler ) ;
325351 }
326352
327353 internal class NullRegistrationSource : IRegistrationSource
@@ -335,9 +361,9 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun
335361 }
336362
337363 [ Fact ]
338- public void RegistrationSourceHookModule_HookIsCalledWhenSourceIsAdded ( )
364+ public void ModuleOverridingRegistrationSourceHookStillReceivesSources ( )
339365 {
340- // Verify the hook is still called when the override is present (regression guard) .
366+ // #1446: regression guard - the overridden hook must still fire for sources .
341367 var module = new RegistrationSourceHookModule ( ) ;
342368 var builder = new ContainerBuilder ( ) ;
343369 builder . RegisterModule ( module ) ;
@@ -371,10 +397,10 @@ protected override void Load(ContainerBuilder builder)
371397 }
372398
373399 [ Fact ]
374- public void IntermediateBaseOverride_HookIsCalledForConcreteSubclass ( )
400+ public void ModuleInheritingHookFromIntermediateBaseStillReceivesRegistrations ( )
375401 {
376- // A module that inherits the override from an intermediate class must still
377- // have its hook invoked (DeclaringType != typeof(Module) covers this case) .
402+ // #1446: the override-detection uses DeclaringType != typeof(Module), so an
403+ // override inherited from an intermediate base class must still fire .
378404 var module = new ConcreteIntermediateModule ( ) ;
379405 var builder = new ContainerBuilder ( ) ;
380406 builder . RegisterModule ( module ) ;
@@ -386,8 +412,9 @@ public void IntermediateBaseOverride_HookIsCalledForConcreteSubclass()
386412 }
387413
388414 [ Fact ]
389- public void IntermediateBaseOverride_SubscribesToRegisteredEvent ( )
415+ public void ModuleInheritingHookFromIntermediateBaseSubscribesToRegisteredEvent ( )
390416 {
417+ // #1446: a module whose override is declared on an intermediate base class must subscribe.
391418 using var registryBuilder = Factory . CreateEmptyComponentRegistryBuilder ( ) ;
392419
393420 new ConcreteIntermediateModule ( ) . Configure ( registryBuilder ) ;
@@ -396,7 +423,7 @@ public void IntermediateBaseOverride_SubscribesToRegisteredEvent()
396423 registryBuilder . Properties . TryGetValue ( MetadataKeys . RegisteredPropertyKey , out var registeredValue )
397424 && registeredValue is not null ;
398425
399- Assert . True ( hasRegisteredHandler , "A module with an override declared on an intermediate base class must still subscribe to the Registered event." ) ;
426+ Assert . True ( hasRegisteredHandler ) ;
400427 }
401428
402429 private class Service1
0 commit comments