Подтвердить что ты не робот

Как примирить IDisposable и IoC?

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

public void DoSomething()
{
    using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource())
    {
        // do something
    }
}

Ответ 3

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

В последнее время я перефразировал вопрос для себя: действительно ли это проблема IoC или проблема с .net framework? В любом случае утилизацию неловко. Он не имеет значимой функциональной цели, только технический. Так что это больше проблема с каркасом, с которой нам приходится иметь дело, чем проблема IoC.

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

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

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

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

Существует одна оговорка: изменение исполнителя контракта из небезопасного в одноразовое будет изменением. В это время интерфейс больше не будет зарегистрирован, а интерфейс к factory. Но я думаю, что это относится и к другому сценарию. Забыть использовать дочерний контейнер с этого момента даст проблемы с памятью. Подход factory приведет к исключению исключения 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

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

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