Как вы согласовываете IDisposable и IoC?

Я наконец оборачиваюсь вокруг IoC и DI в C # и борюсь с некоторыми из краев. Я использую контейнер Unity, но я думаю, что этот вопрос применяется более широко.

Использование контейнера IoC для выдачи экземпляров, которые реализуют IDisposable, пугает меня! Как вы должны знать, если вы должны Dispose ()? Экземпляр, возможно, был создан только для вас (и поэтому вы должны его утилизировать), или это может быть экземпляр, срок жизни которого управляется в другом месте (и поэтому вам лучше этого не делать). Ничто в коде не говорит вам, и на самом деле это может измениться в зависимости от конфигурации !!! Это кажется мне смертельным.

Могут ли какие-либо эксперты IoC описать хорошие способы справиться с этой двусмысленностью?

12.06.2009 16:48:43
Мое решение состоит в том, чтобы использовать IoC с надлежащим и хорошо кодифицированным управлением временем жизни: такие есть у AutoFac и Castle Windsor (хотя они работают немного по-разному); Модуль 2.1 просто дает сбой при работе с переходными процессами при использовании менеджеров времени жизни по умолчанию ..
user2864740 3.03.2015 21:00:06
7 ОТВЕТОВ
РЕШЕНИЕ

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

.. При разрешении служб Autofac отслеживает одноразовые (IDisposable) компоненты, которые разрешаются. В конце рабочей единицы вы избавляетесь от связанной области действия времени жизни, и Autofac автоматически очищает / удаляет разрешенные службы.

7
4.03.2015 17:22:49
Очень интересно. Я ничего не знал о AutoFac. Я не думаю, что было бы слишком сложно сделать что-то подобное с Unity. Мне нужно немного подумать об этом. Спасибо!
Mr. Putty 13.06.2009 07:07:30
Это может быть трудно сделать с Unity, поскольку детерминированное удаление было встроено в архитектуру Autofac с самого начала.
Rinat Abdullin 13.06.2009 17:56:28
Castle Windsor - это еще один контейнер, который позволяет вам делать это, используя субконтейнер, ограниченный образ жизни или явно выпуская компоненты с помощью container.Releaseметода
Krzysztof Kozmic 18.05.2010 11:36:07
StructureMap также имеет концепцию вложенного контейнера для решения этой проблемы. Тем не менее, это только для переходных процессов, он не будет располагать объекты, ограниченные на уровне контекста http (есть вызов метода, который делает это) или Singletons (полностью на ваше усмотрение).
Andy 16.07.2012 17:55:59
Кажется, что AutoFac всегда является IDisposable-lifetimes для каждого контейнера / целой области действия (который работает довольно хорошо и, в отличие от Unity , он предлагает возможную очистку переходных процессов). Castle Windsor может [также] освобождать поддеревья компонентов без необходимости создавать явные области видимости, что полезно при реализации таких вещей, как фабрики. (Но для того, чтобы это работало так, как рекламировалось, и позволяло досрочно выпускать, должны быть явные вызовы "factory.Release", поэтому частично это
user2864740 4.03.2015 17:14:28

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

2
12.06.2009 16:52:48
Это очень неудобно, так как я часто хочу вводить одноразовые предметы. Как правило, семантика IDisposable требует, чтобы создатель / владелец объекта утилизировал его в нужное время. Если за это отвечает контейнер, то пользователь экземпляра должен сообщить контейнеру, когда это будет сделано. Это легко забыть.
Mr. Putty 13.06.2009 07:05:36
Понижено, так как последняя часть вашего ответа плохая. Большинство контейнеров имеют способ справиться с этим, либо вложенные контейнеры, либо что-то вроде ReleaseAndDisposeAllHttpScopedObjectsметода StructureMaps . Вы все еще должны выяснить, как одноразовые материалы будут правильно утилизироваться ... если вы не любите кончаться соединения и тому подобное.
Andy 16.07.2012 18:00:20

Это зависит от структуры DI. Некоторые платформы позволяют вам указать, хотите ли вы использовать общий экземпляр (всегда используя одну и ту же ссылку) для каждой введенной зависимости. В этом случае вы, скорее всего, не хотите распоряжаться.

Если вы можете указать, что хотите внедрить уникальный экземпляр, вы захотите утилизировать (так как он был создан специально для вас). Однако я не так хорошо знаком с Unity - вам придется проверить документы, чтобы узнать, как это сделать. Это часть атрибута MEF и некоторых других, которые я пробовал.

2
12.06.2009 16:55:29

Вы определенно не хотите вызывать Dispose () для объекта, который был внедрен в ваш класс. Вы не можете сделать предположение, что вы единственный потребитель. Лучше всего обернуть ваш неуправляемый объект в некоторый управляемый интерфейс:

public class ManagedFileReader : IManagedFileReader
{
    public string Read(string path)
    {
        using (StreamReader reader = File.OpenRead(path))
        {
            return reader.ReadToEnd();
        }
    }
}

Это всего лишь пример, я бы использовал File.ReadAllText (путь), если бы я пытался прочитать текстовый файл в строку.

Другой подход состоит в том, чтобы ввести фабрику и управлять объектом самостоятельно:

public void DoSomething()
{
    using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource())
    {
        // do something
    }
}
17
18.06.2009 04:35:06
Но я хочу внедрить IDisposable объекты, и, кроме того, мне может понадобиться или не понадобиться Dispose () из них в месте внедрения, в зависимости от конфигурации. Ваш первый пример работает, когда к базовому ресурсу обращаются временно, но не помогает, если ресурс более постоянен. Во втором примере мне кажется странным вводить фабрику таким образом; это одна из основных функций модели IoC. Если я применю IoC к этой концепции, я, похоже, вернусь к исходному вопросу. Я думаю, что сам контейнер должен участвовать в этом, а-ля AutoFac.
Mr. Putty 26.06.2009 16:05:08
@Г-н. Замазка - внедрение зависимостей не устраняет необходимость в фабриках, а только делает ненужным одно (ab) их использование. Например, когда вы не знаете тип конкретной зависимости до времени выполнения или для дорогих условных зависимостей (объектов, которые вам могут даже не понадобиться, для создания которых требуется много ресурсов), вы можете захотеть внедрить фабрику вместо сам объект. В зависимости от поддержки вашей DI-инфраструктуры для IDisposable, фабричная инъекция может быть лучшим способом - она, безусловно, более прозрачна, чем многие альтернативы. (+1)
Jeff Sternal 13.10.2009 13:11:24
Я думаю, что это хороший совет. Никогда не вводите IDisposables напрямую; вместо этого используйте управляемые оболочки - или используйте время жизни, которое позволяет явное удаление в определенных случаях, таких как контекст БД, например HttpContextScoped в StructureMap или PerRequestLifetimeManager в Unity.
L-Four 31.10.2014 08:43:31
@ L-Three И используйте контейнер IoC, который на самом деле понимает срок службы IDisposable ... но при утилизации одноразовых предметов могут возникать причуды. Единственный «верный» метод, который я нашел до сих пор, - это что-то вроде фабрики Castle и [typed]: в то время как переходные процессы будут в конечном итоге освобождены из корня (т.е. без «полной» утечки ресурсов), единственный способ обеспечить немедленный подграф Утилизация осуществляется по стратегии "фабрика. Выпуск". Однако это лучше, чем пытаться работать с IDisposable сервисами напрямую: 1) время жизни все еще обрабатывается IoC; 2) потребитель не зависит от того, является ли компонент одноразовым.
user2864740 4.03.2015 17:30:03

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

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

Поэтому, по-видимому, нет проблем с инъецированными одноразовыми объектами в рамках Unity. Я не знаю о других фреймворках, но полагаю, что пока фреймворк внедрения зависимостей достаточно прочный, он наверняка так или иначе решает эту проблему.

1
19.09.2009 20:28:26
Отличная статья по этому поводу
TrueWill 16.10.2011 15:53:04
Это ужасный подход, ничего «разумного» в том, что нужно вводить и отслеживать объекты, которые нужно утилизировать. Это сложно, потому что в дальнейшем компоненты не могут вручную утилизировать свои введенные зависимости, потому что время жизни неизвестно - синглтон, переходный процесс и т. Д. Сбой Unity (2.x) завершается неудачей в том, что при стандартных временах жизни переходные процессы никогда не удаляются контейнером ,
user2864740 3.03.2015 20:26:54
«В последнем случае ...» Клиент не знает, является ли это первым или последним случаем, и не должен знать. Следует не знать о выбранном сроке службы введенного объекта. Я не согласен с замечанием user2864740 о том, что Unity не работает. Я думаю, что, к сожалению, нам приходится иметь дело с рамочной характеристикой.
Remco te Wierik 29.05.2016 22:39:56

Помещение фасада перед контейнером может решить эту проблему. Кроме того, вы можете расширить его, чтобы отслеживать более насыщенный жизненный цикл, такой как отключение и запуск служб или переходы состояний ServiceHost.

Мой контейнер имеет тенденцию жить в IExtension, который реализует интерфейс IServiceLocator. Это фасад для единства и обеспечивает легкий доступ к услугам WCf. Плюс у меня есть доступ к сервисным событиям из ServiceHostBase.

Код, который вы в итоге получите, попытается увидеть, реализует ли какой-либо зарегистрированный синглтон или созданный тип какой-либо из интерфейсов, отслеживаемых фасадом.

Тем не менее не позволяет своевременно утилизировать, поскольку вы привязаны к этим событиям, но это немного помогает.

Если вы хотите своевременно утилизировать (иначе, теперь после отключения службы). Вы должны знать, что получаемый вами элемент является одноразовым, его удаление является частью бизнес-логики, поэтому IDisposable должен быть частью интерфейса объекта. И, вероятно, должна быть проверка ожиданий, связанных с вызовом метода dispose.

2
12.03.2010 21:04:32
Разве вы не теряете преимущество инъекции конструктора таким образом?
L-Four 31.10.2014 08:38:32

Это меня часто озадачивало. Хотя меня это не устраивало, я всегда приходил к выводу, что лучше никогда не возвращать IDisposable объект временным способом.

Недавно я перефразировал вопрос для себя: действительно ли это проблема IoC или проблема .NET Framework? Утилизация в любом случае неудобна. У него нет значимого функционального назначения, только техническое. Так что это скорее проблема структуры, с которой мы должны иметь дело, чем проблема IoC.

Что мне нравится в DI, так это то, что я могу попросить контракт, предоставляющий мне функциональность, не беспокоясь о технических деталях. Я не владелец. Нет знаний о том, в каком слое он находится. Нет знаний о том, какие технологии требуются для выполнения контракта, не стоит беспокоиться о сроке службы. Мой код выглядит красиво и чисто, и он хорошо тестируется. Я могу реализовать обязанности в слоях, где они принадлежат.

Так что если есть исключение из этого правила, которое требует от меня организации времени жизни, давайте сделаем это исключение. Нравится мне это или нет. Если объект, реализующий интерфейс, требует от меня его утилизации, я хочу знать об этом с тех пор, как меня заставляют использовать объект как можно короче. Уловка, решая это с помощью дочернего контейнера, который утилизируется через некоторое время, может все еще заставить меня поддерживать объект дольше, чем я должен. Допустимое время жизни объекта определяется при регистрации объекта. Не благодаря функциональности, которая создает дочерний контейнер и удерживает его в течение определенного периода.

Поэтому, пока мы, разработчики, должны беспокоиться об утилизации (изменится ли это когда-нибудь?), Я постараюсь ввести как можно меньше временных одноразовых объектов. 1. Я пытаюсь сделать объект не IDisposable, например, не сохраняя одноразовые объекты на уровне класса, но в меньшей области. 2. Я пытаюсь сделать объект многоразовым, чтобы можно было применить другой менеджер времени жизни.

Если это невозможно, я использую фабрику, чтобы указать, что пользователь введенного договора является владельцем и должен взять на себя ответственность за него.

Есть одно предостережение: изменение исполнителя контракта с одноразового на одноразовое будет серьезным изменением. В это время интерфейс больше не будет зарегистрирован, но интерфейс к фабрике. Но я думаю, что это относится и к другим сценариям. Если вы забудете использовать дочерний контейнер, то с этого момента возникнут проблемы с памятью. Заводской подход вызовет исключительную ситуацию разрешения IoC.

Пример кода:

using System;
using Microsoft.Practices.Unity;

namespace Test
{
    // Unity configuration
    public class ConfigurationExtension : UnityContainerExtension
    {
        protected override void Initialize()
        {
            // Container.RegisterType<IDataService, DataService>(); Use factory instead
            Container.RegisterType<IInjectionFactory<IDataService>, InjectionFactory<IDataService, DataService>>();
        }
    }

    #region General utility layer

    public interface IInjectionFactory<out T>
        where T : class
    {
        T Create();
    }

    public class InjectionFactory<T2, T1> : IInjectionFactory<T2>
        where T1 : T2
        where T2 : class

    {
        private readonly IUnityContainer _iocContainer;

        public InjectionFactory(IUnityContainer iocContainer)
        {
            _iocContainer = iocContainer;
        }

        public T2 Create()
        {
            return _iocContainer.Resolve<T1>();
        }
    }

    #endregion

    #region data layer

    public class DataService : IDataService, IDisposable
    {
        public object LoadData()
        {
            return "Test data";
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                /* Dispose stuff */
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

    #endregion

    #region domain layer

    public interface IDataService
    {
        object LoadData();
    }

    public class DomainService
    {
        private readonly IInjectionFactory<IDataService> _dataServiceFactory;

        public DomainService(IInjectionFactory<IDataService> dataServiceFactory)
        {
            _dataServiceFactory = dataServiceFactory;
        }

        public object GetData()
        {
            var dataService = _dataServiceFactory.Create();
            try
            {
                return dataService.LoadData();
            }
            finally
            {
                var disposableDataService = dataService as IDisposable;
                if (disposableDataService != null)
                {
                    disposableDataService.Dispose();
                }
            }
        }
    }

    #endregion
}
4
5.03.2015 13:49:41