Как очистить экземпляры, созданные SimpleInjector во время проверки? - PullRequest
4 голосов
/ 09 января 2020

Как часть создания моего контейнера 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();

Это похоже на неприятный хак. Сначала ему нужно явно привести к типу реализации, затем он вызывает метод, который в противном случае вообще не должен был бы существовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...