MEF сохраняет ссылки на NonShared IDisposable, не позволяя им собираться GC

Я столкнулся с некоторой проблемой в жизни части MEF, которая вызывает утечку памяти в моем приложении Prism.

Мое приложение экспортирует представления и режимы просмотра с установкой PartCreationPolicy на CreationPolicy.NonShared. Представления и viewmodels наследуют от ViewBase и ViewModelBase соответственно, что реализует IDisposable.

Теперь, поскольку мои части реализуют IDisposable, ссылка на них хранится в контейнере, что заставляет их не освобождаться сборщиком мусора. Согласно документации MEF по времени жизни, это по дизайну:

Контейнер не будет содержать ссылки на части, которые он создает, если не выполнено одно из следующих утверждений:

  • Часть отмечена как Shared
  • Часть реализует IDisposable
  • Один или несколько импорта настроены так, чтобы разрешить перекомпоновку

Как я могу заставить MEF не ссылаться на эти части? Есть ли атрибут, который я могу использовать, чтобы MEF знал, что я не хочу, чтобы он сохранял ссылку на мою часть, даже если она реализует IDisposable?

Обе стратегии, описанные в этой статье, не кажутся хорошими для меня решениями:

  • ReleaseExport требуется объект Export в качестве параметра, который я не знаю, как обеспечить. У меня есть экземпляр моего взгляда, но я не знаю, для чего был контракт, который использовался для создания представления. Было бы здорово, если бы была перегрузка для ReleaseExport, которая могла бы получить любой объект, созданный контейнером.
  • Использование дочернего контейнера не похоже на естественный вариант.

Любая помощь будет принята с благодарностью.

Ответ 1

Если Prism не поддерживает какое-то время жизни для объектов вида, здесь нет решения, кроме как удалить IDisposable из списка интерфейсов, отображаемых в представлении.

Существует три подхода MEF для решения этой проблемы, упомянутых другими респондентами:

  • ExportFactory<T>
  • Контейнеры для детей
  • ReleaseExport()

Все они требуют некоторой работы со стороны кода, который запрашивает исходный экспорт - в этом случае код внутри Prism. Это имеет смысл, так как нежелательно, чтобы код, потреблявший объект, должен был знать, как и когда он был создан.

В MEF нет ReleaseExportedObject(), поскольку многократное (например, свойство) экспорт может возвращать одно и то же значение; он может быть логически возможен, но добавленная сложность делает его маловероятным для решения MEF в обозримом будущем.

Надеюсь, что это поможет; Я повторил этот вопрос "призму", так как я уверен, что другие в сообществе Prism столкнутся с этим и смогут дать совет.

Ответ 2

Когда вы реализуете IDisposable, вы говорите, что тип должен быть очищен детерминированным способом (вызывая IDisposable.Dispose, а не случайным образом, когда сборщик мусора решает, что это время.

В вашем случае модели просмотра будут удаляться только при размещении контейнера, который, вероятно, не является тем, что вы хотите сделать. Чтобы обойти это, я вижу два возможных решения:

  • Не используйте IDisposable в моделях просмотра. По-видимому, вам все равно, когда их убирают в любом случае, поэтому зачем их IDisposable?

  • Вместо того, чтобы позволить контейнеру создавать каждую модель представления без общего доступа, вы можете использовать класс общей модели представления factory. Затем вы можете применить этот класс к владельцам моделей просмотра, чтобы позволить владельцам явно создавать модели представлений. Предположительно, эти владельцы также знали бы, когда нужно распоряжаться моделями просмотра.

В принципе, если что-то одноразовое, что также должно быть разумной точкой в ​​вашем коде, где вам нужно распоряжаться тем, что является одноразовым.

Ответ 3

Вы должны создать эти экземпляры через импортированную ExportFactory <T>. Затем у вас будет необходимый элемент управления для их удаления с помощью ExportLifetimeContext<T>.Dispose().

Однако, это доступно только из коробки в следующей версии .NET(4.5) или в последних выпусках MEF предварительного просмотра на codeplex. (В более старых версиях MEF та же функциональность была реализована в качестве образца и называлась PartCreator, как описано в этом сообщении в блоге.)

Ответ 4

Все остальные ответы дают хорошие способы обойти эту проблему, но в итоге я в конечном итоге использовал свой собственный интерфейс ICleanup вместо IDisposable. Это, конечно, не подходит для всех.