Od jakiegoś czasu Castle Windsor to mój ulubiony kontener IoC. Jutro co prawda może się nim stać Ninject lub Autofac, ale nie dlatego że mi źle z Windsor, a raczej z czystej ciekawości. Przejdźmy do rzeczy.
Jak generalnie wyobrażamy sobie pracę z kontenerem IoC? Z reguły są dwa kroki do wykonania: rejestracja typu i pobranie instancji tego typu. W Windsorze w najprostszej formie wygląda to tak:
Rejestracja:
container.Register(
Component.For<ISecurityService>()
.ImplementedBy<SecurityService>()
);
Pobranie:
ISecurityService securityService = container.Resolve<ISecurityService>();
Niby wszystko jest OK i jest to jakaś alternatywa dla tego:
ISecurityService securityService = new SecurityService();
No tak, ale ktoś mógłby spytać o długość życia obiektu securityService. I w tym momencie dotykamy bardzo ważnej kwestii kontenerów IoC – LifeStyle. Każdy kontener IoC, Windsor także, posiada kilka wbudowanych elementarnych LifeStyle’i tj.: Singleton, Transient, Scope, PerWebRequest, itd.
LifeStyle określa zasięg obiektów instancjonowanych przez kontener oraz mówi kiedy będą niszczone. Singleton jest domyślnym lifestyle’m w Windsorze. Oznacza to, że będzie wytworzona jedna instancja przy pierwszym wywołaniu Resolve i reużywana przy kolejnych wywołaniach Resolve.
Zupełnym przeciwieństwem do Singletona jest Transient – przy każdym wywołaniu Resolve kontener zwraca nową instancję.
Rejestracja naszego serwisu dla Transient wyglądałaby tak:
container.Register(
Component.For<ISecurityService>()
.ImplementedBy<SecurityService>()
.LifeStyle.Transient
);
Pobieramy tak zarejestrowany serwis, robimy z nim co mamy do zrobienia i zapominamy … Garbage Collector go zniszczy. Fałsz! Mina! Memory leak! Nic takiego nie nastąpi dopóki nie zniszczymy naszego kontenera IoC (jeśli w ogóle to robimy?). To jest dość często spotykana mina na którą się można nadziać. Windsor może trzymać referencję do obiektu transient, a więc Garbage Collector nie będzie w stanie zwolnić zasobów. Dla wszystkich obiektów transient najwyższego rzędu które zostaną przez nas jawnie pobrane z kontenera IoC musimy wykonać Release. Poprawna sekwencja powinna wyglądać tak:
Rejestracja:
container.Register(
Component.For<ISecurityService>()
.ImplementedBy<SecurityService>()
.LifeStyle.Transient
);
Pobranie:
ISecurityService securityService = container.Resolve<ISecurityService>();
Praca z serwisem:
securityService.Authenticate(authDTO);
Zwolnienie:
container.Release(securityService);
I tak sobie myślę, że potencjalnie wbrew pozorom najbardziej zagrożeni nadzianiem się na tę minę nie są początkujący użytkownicy kontenerów IoC (jest szansa że oni uważnie czytają dokumentację), a raczej tacy którzy przesiadają się na Windsora z jakiś „starszych” rozwiązań, myśląc że to tylko kolejny kontener z nieco innym API. Mea culpa, przyznaję się, kiedyś sam się nadziałem, ale widzę że nie jestem sam ;).