Как вы согласовываете IDisposable и IoC? - PullRequest
41 голосов
/ 12 июня 2009

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

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

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

Ответы [ 7 ]

17 голосов
/ 18 июня 2009

Вы определенно не хотите вызывать 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
    }
}
7 голосов
/ 12 июня 2009

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

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

4 голосов
/ 05 марта 2015

Меня это тоже часто озадачивало. Хотя я не был доволен этим, я всегда приходил к выводу, что лучше никогда не возвращать 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
}
2 голосов
/ 13 марта 2010

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

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

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

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

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

2 голосов
/ 12 июня 2009

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

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

2 голосов
/ 12 июня 2009

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

1 голос
/ 20 сентября 2009

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

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

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

...