Как часть создания моего контейнера SimpleInjector, я следовал рекомендованным методам и позвонил container.Verify()
, чтобы проверить, что мои регистрации типов имеют смысл.
Это работает хорошо и обнаружило ряд ошибок, которые я Я сделал - но он также создает мусор, который потом остается вокруг, и я бы хотел его убрать.
Один из моих классов - одноэлементный концентратор событий , который используется для маршрутизации сообщений между другими переходные компоненты; эти другие компоненты принимают концентратор событий в своем конструкторе, создают подписку для получения сообщений, которые они заинтересованы в получении, а затем Dispose()
подписку, когда они завершены.
Создает вызов container.Verify()
по одному объекту каждого типа, что приводит к тому, что некоторые из этих преходящих экземпляров задерживаются, поскольку концентратор событий все еще знает об их подписках.
В настоящий момент я обошел проблему, немедленно прекратив все подписки вручную после вызова Verify()
, до запуска приложения. Однако это похоже на проблему, которая должна быть уже решена, хотя я не смог найти ответ в документах, здесь, в Переполнении стека, или с помощью поиска.
Возможно, использование ограниченного стиля жизни - это решение? Они не были актуальны, потому что я создаю приложение WPF, но если бы я знал ответ, я бы не стал спрашивать здесь!
Обновление, 12 января - По запросу @ Стивен, вот код, демонстрирующий мою проблему.
Я попытался (и не смог) продемонстрировать проблему с помощью чего-то, что было и компилируемым, и достаточно коротким для совместного использования; вместо этого я показываю некоторые фрагменты кода из реального проекта. Если вы хотите увидеть все это, проект WordTutor находится на GitHub .
В основе моего приложения есть синглтон IReduxStore<T>
, который одновременно инкапсулирует состояние приложения и действует как своего рода центр событий. Другие классы подписываются на хранилище, чтобы получать упреждающие уведомления при изменении состояния приложения.
Вот IReduxStore<T>
, в сравнении с основными:
// IReduxStore.cs
public interface IReduxStore<T>
{
T State { get; }
void Dispatch(IReduxMessage message);
IDisposable SubscribeToReference<V>(
Func<T, V?> referenceReader,
Action<V?> whenChanged)
where V : class, IEquatable<V>?;
}
Подписки реализуют IDisposable
как удобный и идиоматический c способ детерминированной c очистки, когда подписка больше не требуется.
Хранилище зарегистрировано как одноэлементное, привязанное к указанному c состоянию:
// Program.cs
container.RegisterSingleton<
IReduxStore<WordTutorApplication>,
ReduxStore<WordTutorApplication>>();
Реализация IReduxStore<T>
сохраняет все подписки:
private readonly HashSet<ReduxSubscription<T>> _subscriptions
= new HashSet<ReduxSubscription<T>>();
Они удаляются из HashSet
при утилизации.
Многие из моих моделей ViewModels принимают IReduxStore<WordTutorApplication>
своим конструкторам, чтобы они могли подписаться на обновления:
// VocabularyBrowserViewModel.cs
public sealed class VocabularyBrowserViewModel : ViewModelBase
{
private readonly IReduxStore<WordTutorApplication> _store;
private readonly IDisposable _screenSubscription;
private readonly IDisposable _vocabularySubscription;
public VocabularyBrowserViewModel(IReduxStore<WordTutorApplication> store)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
// ... elided ...
_screenSubscription = _store.SubscribeToReference(
app => app.CurrentScreen as VocabularyBrowserScreen,
RefreshFromScreen);
_vocabularySubscription = _store.SubscribeToReference(
app => app.VocabularySet,
RefreshFromVocabularySet);
// ... elided ...
}
// ... elided ...
}
ViewModels зарегистрированы как временные, поскольку каждому окну нужен уникальный экземпляр:
// Program.cs
var desktopAssembly = typeof(WordTutorWindow).Assembly;
container.Collection.Register<ViewModelBase>(desktopAssembly);
ViewModels выпускают свои подписки проактивно когда они больше не нужны:
// VocabularyBrowserViewModel.cs
private void RefreshFromScreen(VocabularyBrowserScreen? screen)
{
if (screen == null)
{
_screenSubscription.Dispose();
_vocabularySubscription.Dispose();
return;
}
Selection = screen.Selection;
Modified = screen.Modified;
}
Когда в контейнере SimpleInjector вызывается Verify()
, создается образец каждого объекта, включая динг синглтон IReduxStore<T>
. Также создаются временные модели представления (например, VocabularyBrowserViewModel
, показанные выше), но эти экземпляры остаются live , поскольку их подписки по-прежнему хранятся в магазине.
I попытался реализовать IDisposable
на ViewModels, но поскольку их стиль жизни - transient , единственным эффектом было генерирование дополнительного предупреждения при вызове Verify()
.
Update II, 12 января :
Обходной путь, который у меня есть на данный момент, состоит в том, чтобы вручную удалить все подписки как часть запуска приложения после успешной инициализации контейнера:
var store = (ReduxStore<WordTutorApplication>)
container.GetInstance<IReduxStore<WordTutorApplication>>();
store.ClearSubscriptions();
Это похоже на неприятный хак. Сначала ему нужно явно привести к типу реализации, затем он вызывает метод, который в противном случае вообще не должен был бы существовать.