У меня есть вопрос, и я собираюсь пометить это субъективно , поскольку, как мне кажется, это превращается в дискуссию. Я надеюсь на некоторые хорошие идеи или провокаторов. Я прошу прощения за многословный вопрос, но вы должны знать контекст.
Вопрос в основном:
- Как вы относитесь к конкретным типам по отношению к контейнерам IoC? В частности, кто отвечает за их удаление, если они требуют утилизации и как эти знания распространяются на вызывающий код?
Требуется ли, чтобы они были идентифицируемыми? Если нет, то является ли этот код перспективным, или вы не можете использовать одноразовые предметы? Если вы применяете IDisposable-требования к интерфейсам и конкретным типам для обеспечения будущего, кто несет ответственность за объекты, внедренные как часть вызовов конструктора?
Редактировать : я принял ответ @ Крис Баллард , так как он наиболее близок к подходу, с которым мы в итоге пришли.
По сути, мы всегда возвращаем тип, который выглядит следующим образом:
public interface IService<T> : IDisposable
where T: class
{
T Instance { get; }
Boolean Success { get; }
String FailureMessage { get; } // in case Success=false
}
Затем мы возвращаем объект, реализующий этот интерфейс, как из .Resolve, так и .TryResolve, так что то, что мы получаем в вызывающем коде, всегда имеет один и тот же тип.
Теперь объект, реализующий этот интерфейс, IService<T>
является IDisposable, и всегда должен быть удален. Программист не может решить, должен ли объект IService<T>
быть удален или нет.
Однако, и это важная часть, должен ли экземпляр службы быть удален или нет, что знания запекаются в объект, реализующий IService<T>
, так что если это сервис с фабричной областью (т. Е. Каждый вызов Resolve заканчивается с новым экземпляром службы), тогда экземпляр службы будет удален при удалении объекта IService<T>
.
Это также позволило поддерживать другие специальные области, такие как пул. Теперь мы можем сказать, что нам нужно минимум 2 экземпляра службы, максимум 15 и обычно 5, что означает, что каждый вызов .Resolve будет либо извлекать экземпляр службы из пула доступных объектов, либо создавать новый. И затем, когда объект IService<T>
, который содержит объединенную службу, удаляется, экземпляр службы освобождается обратно в его пул.
Конечно, это заставило весь код выглядеть так:
using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
service.Instance.DoSomething();
}
но это чистый подход, и он имеет один и тот же синтаксис, независимо от типа службы или конкретного используемого объекта, поэтому мы выбрали это в качестве приемлемого решения.
Далее следует оригинальный вопрос для потомков
Длинный вопрос приходит сюда:
У нас есть контейнер IoC, который мы используем, и недавно мы обнаружили, что составляет проблему.
В коде, отличном от IoC, когда мы хотели использовать, скажем, файл, мы использовали такой класс:
using (Stream stream = new FileStream(...))
{
...
}
Не было никаких сомнений относительно того, был ли этот класс чем-то, что содержало ограниченный ресурс, или нет, так как мы знали, что файлы должны быть закрыты, и сам класс реализовал IDisposable. Правило состоит в том, что каждый класс, из которого мы создаем объект, реализующий IDisposable, должен быть удален. Никаких вопросов не было задано. Пользователь этого класса не может решить, является ли вызов Dispose необязательным или нет.
Хорошо, давайте перейдем к первому шагу в направлении контейнера IoC. Давайте предположим, что мы не хотим, чтобы код говорил напрямую с файлом, а вместо этого пройдем один уровень косвенности. Давайте назовем этот класс BinaryDataProvider для этого примера. Внутри класса используется поток, который все еще является одноразовым объектом, поэтому приведенный выше код будет изменен на:
using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
...
}
Это мало что меняет. Знание того, что класс реализует IDisposable, все еще здесь, без вопросов, нам нужно вызвать Dispose.
Но давайте предположим, что у нас есть классы, предоставляющие данные, которые сейчас не используют такие ограниченные ресурсы.
Приведенный выше код может быть записан как:
BinaryDataProvider provider = new BinaryDataProvider();
...
Хорошо, пока все хорошо, но здесь идет главный вопрос. Предположим, мы хотим использовать контейнер IoC для внедрения этого провайдера вместо зависимости от конкретного конкретного типа.
Тогда код будет:
IBinaryDataProvider provider =
ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...
Обратите внимание, что я предполагаю, что существует независимый интерфейс, через который мы можем получить доступ к объекту.
С учетом вышеуказанного изменения, что если мы позже захотим использовать объект, от которого действительно нужно избавиться? Ни один из существующих кодов, разрешающих этот интерфейс, не написан для удаления объекта, и что теперь?
Как мы видим, мы должны выбрать одно решение:
- Реализация проверки во время выполнения, которая проверяет, что если конкретный тип, который регистрируется, реализует IDisposable, требует, чтобы интерфейс, через который он открывается, также реализовывал IDisposable. Это не хорошее решение
- Прежде чем накладывать ограничения на используемые интерфейсы, они всегда должны наследоваться от IDisposable, чтобы быть ориентированными на будущее
- Обеспечить выполнение, чтобы никакие конкретные типы не могли быть IDisposable, так как это специально не обрабатывается кодом, использующим контейнер IoC
- Просто оставьте это на усмотрение программиста, чтобы проверить, реализует ли объект IDisposable и "делать правильные вещи"?
- Есть ли другие?
Кроме того, как насчет внедрения объектов в конструкторы? Наш контейнер и некоторые другие контейнеры, которые мы рассмотрели, способны вводить свежий объект в параметр в конструктор конкретного типа. Например, если нашему BinaryDataProvider
нужен объект, который реализует интерфейс ILogging
, если мы применяем IDispose- «способность» к этим объектам, кто несет ответственность за удаление объекта регистрации?
Что вы думаете? Я хочу мнения, хорошие и плохие.