@@ -675,6 +675,144 @@ public void DecoratorAndDecoratedBothDisposedWhenSingleInstance()
675675 Assert . Equal ( 1 , decorated . DisposeCallCount ) ;
676676 }
677677
678+ [ Fact ]
679+ public void DecoratorNotDisposedWhenDecoratedServiceIsExternallyOwned ( )
680+ {
681+ // #1402: A decorator wrapping an ExternallyOwned service should itself
682+ // not be disposed by the lifetime scope.
683+ var builder = new ContainerBuilder ( ) ;
684+ builder . RegisterType < DisposableImplementor > ( )
685+ . As < IDecoratedService > ( )
686+ . ExternallyOwned ( ) ;
687+ builder . RegisterDecorator < DisposableDecorator , IDecoratedService > ( ) ;
688+ var container = builder . Build ( ) ;
689+
690+ DisposableDecorator decorator ;
691+ DisposableImplementor decorated ;
692+
693+ using ( var scope = container . BeginLifetimeScope ( ) )
694+ {
695+ var instance = scope . Resolve < IDecoratedService > ( ) ;
696+ decorator = ( DisposableDecorator ) instance ;
697+ decorated = ( DisposableImplementor ) instance . Decorated ;
698+ }
699+
700+ Assert . Equal ( 0 , decorator . DisposeCallCount ) ;
701+ Assert . Equal ( 0 , decorated . DisposeCallCount ) ;
702+ }
703+
704+ [ Fact ]
705+ public void StackedDecoratorsNotDisposedWhenDecoratedServiceIsExternallyOwned ( )
706+ {
707+ // #1402: When several decorators are stacked over an ExternallyOwned
708+ // service, the ownership of the underlying component governs the entire
709+ // chain - none of the decorators should be disposed by the scope.
710+ var builder = new ContainerBuilder ( ) ;
711+ builder . RegisterType < DisposableImplementor > ( )
712+ . As < IDecoratedService > ( )
713+ . ExternallyOwned ( ) ;
714+ builder . RegisterDecorator < DisposableDecorator , IDecoratedService > ( ) ;
715+ builder . RegisterDecorator < DisposableDecoratorB , IDecoratedService > ( ) ;
716+ var container = builder . Build ( ) ;
717+
718+ DisposableDecoratorB outer ;
719+ DisposableDecorator inner ;
720+ DisposableImplementor decorated ;
721+
722+ using ( var scope = container . BeginLifetimeScope ( ) )
723+ {
724+ var instance = scope . Resolve < IDecoratedService > ( ) ;
725+ outer = ( DisposableDecoratorB ) instance ;
726+ inner = ( DisposableDecorator ) outer . Decorated ;
727+ decorated = ( DisposableImplementor ) inner . Decorated ;
728+ }
729+
730+ Assert . Equal ( 0 , outer . DisposeCallCount ) ;
731+ Assert . Equal ( 0 , inner . DisposeCallCount ) ;
732+ Assert . Equal ( 0 , decorated . DisposeCallCount ) ;
733+ }
734+
735+ [ Fact ]
736+ public void StackedDecoratorsAllDisposedWhenDecoratedServiceOwnedByLifetimeScope ( )
737+ {
738+ // #1402 regression guard: stacked decorators over a normally-owned
739+ // service must continue to be disposed along with the decorated instance.
740+ var builder = new ContainerBuilder ( ) ;
741+ builder . RegisterType < DisposableImplementor > ( )
742+ . As < IDecoratedService > ( ) ;
743+ builder . RegisterDecorator < DisposableDecorator , IDecoratedService > ( ) ;
744+ builder . RegisterDecorator < DisposableDecoratorB , IDecoratedService > ( ) ;
745+ var container = builder . Build ( ) ;
746+
747+ DisposableDecoratorB outer ;
748+ DisposableDecorator inner ;
749+ DisposableImplementor decorated ;
750+
751+ using ( var scope = container . BeginLifetimeScope ( ) )
752+ {
753+ var instance = scope . Resolve < IDecoratedService > ( ) ;
754+ outer = ( DisposableDecoratorB ) instance ;
755+ inner = ( DisposableDecorator ) outer . Decorated ;
756+ decorated = ( DisposableImplementor ) inner . Decorated ;
757+ }
758+
759+ Assert . Equal ( 1 , outer . DisposeCallCount ) ;
760+ Assert . Equal ( 1 , inner . DisposeCallCount ) ;
761+ Assert . Equal ( 1 , decorated . DisposeCallCount ) ;
762+ }
763+
764+ [ Fact ]
765+ public void GenericDecoratorNotDisposedWhenDecoratedServiceIsExternallyOwned ( )
766+ {
767+ // #1402: The open-generic decorator path also runs through the decorator
768+ // middleware, so an ExternallyOwned decorated component must prevent the
769+ // generic decorator from being disposed by the scope.
770+ var builder = new ContainerBuilder ( ) ;
771+ builder . RegisterGeneric ( typeof ( DisposableGenericComponent < > ) )
772+ . As ( typeof ( IDisposableGenericService < > ) )
773+ . ExternallyOwned ( ) ;
774+ builder . RegisterGenericDecorator ( typeof ( DisposableGenericDecorator < > ) , typeof ( IDisposableGenericService < > ) ) ;
775+ var container = builder . Build ( ) ;
776+
777+ DisposableGenericDecorator < int > decorator ;
778+ DisposableGenericComponent < int > decorated ;
779+
780+ using ( var scope = container . BeginLifetimeScope ( ) )
781+ {
782+ var instance = scope . Resolve < IDisposableGenericService < int > > ( ) ;
783+ decorator = ( DisposableGenericDecorator < int > ) instance ;
784+ decorated = ( DisposableGenericComponent < int > ) decorator . Decorated ;
785+ }
786+
787+ Assert . Equal ( 0 , decorator . DisposeCallCount ) ;
788+ Assert . Equal ( 0 , decorated . DisposeCallCount ) ;
789+ }
790+
791+ [ Fact ]
792+ public void GenericDecoratorAndDecoratedBothDisposedWhenOwnedByLifetimeScope ( )
793+ {
794+ // #1402 regression guard: open-generic decorator over a normally-owned
795+ // service must continue to be disposed along with the decorated instance.
796+ var builder = new ContainerBuilder ( ) ;
797+ builder . RegisterGeneric ( typeof ( DisposableGenericComponent < > ) )
798+ . As ( typeof ( IDisposableGenericService < > ) ) ;
799+ builder . RegisterGenericDecorator ( typeof ( DisposableGenericDecorator < > ) , typeof ( IDisposableGenericService < > ) ) ;
800+ var container = builder . Build ( ) ;
801+
802+ DisposableGenericDecorator < int > decorator ;
803+ DisposableGenericComponent < int > decorated ;
804+
805+ using ( var scope = container . BeginLifetimeScope ( ) )
806+ {
807+ var instance = scope . Resolve < IDisposableGenericService < int > > ( ) ;
808+ decorator = ( DisposableGenericDecorator < int > ) instance ;
809+ decorated = ( DisposableGenericComponent < int > ) decorator . Decorated ;
810+ }
811+
812+ Assert . Equal ( 1 , decorator . DisposeCallCount ) ;
813+ Assert . Equal ( 1 , decorated . DisposeCallCount ) ;
814+ }
815+
678816 [ Fact ]
679817 public void DecoratorAppliedOnlyOnceToComponentWithExternalRegistrySource ( )
680818 {
@@ -1413,6 +1551,25 @@ public void Dispose()
14131551 }
14141552 }
14151553
1554+ // ReSharper disable once ClassNeverInstantiated.Local
1555+ private sealed class DisposableDecoratorB : Decorator , IDisposable
1556+ {
1557+ public DisposableDecoratorB ( IDecoratedService decorated )
1558+ : base ( decorated )
1559+ {
1560+ }
1561+
1562+ public int DisposeCallCount
1563+ {
1564+ get ; private set ;
1565+ }
1566+
1567+ public void Dispose ( )
1568+ {
1569+ DisposeCallCount ++ ;
1570+ }
1571+ }
1572+
14161573 // ReSharper disable once ClassNeverInstantiated.Local
14171574 private sealed class DisposableImplementor : IDecoratedService , IDisposable
14181575 {
@@ -1533,6 +1690,48 @@ public IGenericService<T> Decorated
15331690 }
15341691 }
15351692
1693+ private interface IDisposableGenericService < T >
1694+ {
1695+ }
1696+
1697+ // ReSharper disable once ClassNeverInstantiated.Local
1698+ private sealed class DisposableGenericComponent < T > : IDisposableGenericService < T > , IDisposable
1699+ {
1700+ public int DisposeCallCount
1701+ {
1702+ get ; private set ;
1703+ }
1704+
1705+ public void Dispose ( )
1706+ {
1707+ DisposeCallCount ++ ;
1708+ }
1709+ }
1710+
1711+ // ReSharper disable once ClassNeverInstantiated.Local
1712+ private sealed class DisposableGenericDecorator < T > : IDisposableGenericService < T > , IDisposable
1713+ {
1714+ public DisposableGenericDecorator ( IDisposableGenericService < T > decorated )
1715+ {
1716+ Decorated = decorated ;
1717+ }
1718+
1719+ public IDisposableGenericService < T > Decorated
1720+ {
1721+ get ;
1722+ }
1723+
1724+ public int DisposeCallCount
1725+ {
1726+ get ; private set ;
1727+ }
1728+
1729+ public void Dispose ( )
1730+ {
1731+ DisposeCallCount ++ ;
1732+ }
1733+ }
1734+
15361735 private class ConditionalShouldDecorate
15371736 {
15381737 public bool ShouldDecorate
0 commit comments