Как создать пользовательскую область Ninject, которая возвращает тот же объект, пока этот объект не будет удален? - PullRequest
6 голосов
/ 26 апреля 2011

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

Я бы хотел, чтобы каждый раз возвращался один объект.Другими словами:

  1. Первый вызов Get () создает новый объект и возвращает его.
  2. Последующие вызовы Get () возвращают тот же экземпляр.
  3. Объект удален.
  4. Первый вызов Get () после удаления объекта создает новый / второй объект и возвращает его.
  5. Последующие вызовы Get () возвращают созданный объектна шаге 4.

РЕДАКТИРОВАТЬ: Эту проблему на самом деле довольно просто решить, используя провайдеров и заставляя рассматриваемый объект вызывать событие при утилизации.Мне было любопытно, если бы был способ сделать это, используя области действия в Ninject, и оставлю этот вопрос здесь, потому что ответ Стивена превосходен.

Ответы [ 2 ]

6 голосов
/ 26 апреля 2011

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

Вы просто не можете настроить объект, который будет продлен после утилизации, из-за условий гонки.Представьте себе следующий сценарий:

  1. Поток 1 запрашивает экземпляр из контейнера.
  2. Это первый запрос, и контейнер создаст новый экземпляр.
  3. Поток2 запрашивает экземпляр из контейнера
  4. Контейнер возвращает экземпляр, созданный на шаге 2.
  5. Поток 1 выполняется с экземпляром и вызывает Dispose.
  6. Поток 2начинает использовать экземпляр, но экземпляр удаляется и выдает исключение.

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

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

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

Общая проблема здесь заключается в том, что трудно создать объекты, реализующие IDisposable, которые на самом деле поточно-ориентированы.

Если вы действительно этого хотите, вы можете попробовать создать декоратор, который выполняет подсчет ссылок.Посмотрите, например, на декоратор ниже.Обертка IService и реализация IService.IService реализует IDisposable.Декоратор принимает делегат Func<IService>, который позволяет создавать экземпляры.Создание и удаление объектов защищено оператором lock, а декоратор и считает ссылки на него вызывающими.Он удалит объект и создаст новый после того, как последний потребитель распорядился декоратором.

public class ScopedServiceDecorator : IService
{
    private readonly object locker = new object();
    private Func<IService> factory;
    private IService currentInstance;
    private int referenceCount;

    public ScopedServiceDecorator(Func<IService> factory)
    {
        this.factory = factory;
    }
    public void SomeOperation()
    {
        IService instance;
        lock (this.locker)
        {
            instance = this.GetInstance();
            this.referenceCount++;
        }

        instance.SomeOperation();
    }

    public void Dispose()
    {
        IService instance = null;

        lock (this.locker)
        {
            this.referenceCount--;

            if (this.referenceCount == 0)
            {
                instance = this.wrappedService;
                this.wrappedService = null;
            }
        }

        // Dispose the object outside the lock for performance.
        if (instance != null)
        {
            instance.Dispose();
        }
    }

    private IService GetInstance()
    {
        if (this.wrappedService == null)
        {
            this.wrappedService = this.factory();
        }

        return this.wrappedService;
    }
}

Обратите внимание, что эта реализация все еще имеет недостатки из-за следующих причин:

  1. Вызов Dispose несколько раз прерывает работу декоратора.
  2. Когда потребители вызывают SomeOperation несколько раз (или IService имеет несколько методов), реализация прерывается.

Довольно сложно создать декоратор, который функционирует так, как ожидалось.Один из простых способов сделать это - сериализовать доступ к объекту, но когда вы делаете это, вы, вероятно, захотите использовать один экземпляр для каждого потока.Это было бы намного проще.

Надеюсь, это поможет.

1 голос
/ 29 апреля 2011

Я знаю, что это решено, но ... @ Ответ Стивена не указывает на то, что в Ninject есть механизм InScope, который учитывает аспекты того, что вы ищете.

Взгляните на статью Cache and Collect Нейта Кохари о том, как можно выполнить определение в Ninject 2.

Далее, посмотрите на исходный источник и посмотрите, как реализован InRequestScope (включая способ подключения). На 2.3-4 запланирована некоторая работа, чтобы обобщить, как это работает, чтобы позволить его использовать для некоторых сложных сценариев хостинга.

Когда вы посмотрите на эти две ссылки, задайте вопрос в списке рассылки njectject, и у вас определенно будет решение.

...