Использование внедрения зависимостей для классов, созданных во время выполнения

У меня есть два класса: ImportBase.cs (родительский) и ImportFleet.cs (дочерний), которые в будущем будут импортировать файл CSV. Каждый дочерний элемент ImportBase будет реализовывать другую реализацию фактического кода импорта. Соответствующий дочерний класс для использования определяется в классе службы, где создается экземпляр правильного класса и вызывается метод импорта. Все идет хорошо до этого момента.

Проблема в том, что я также хочу внедрить некоторые классы репозитория Dependency Inject в ImportBase и его унаследованные классы (как я пытался в приведенном ниже коде):

ImportBase.cs

namespace WebApi.Services.Import.Investments
{
    interface IImport
    {
        public void Import(IFormFile file, int UserId);
    }

    public abstract class ImportBase : IImport
    {
        public abstract void Import(IFormFile file, int UserId);

        protected List<InvestmentTransactionType> transactionTypes = new();

        protected IInvestmentEntityRepository _investmentEntityRepository;

        public ImportBase(IInvestmentEntityRepository investmentEntityRepository)
        {
            _investmentEntityRepository = investmentEntityRepository;
        }
    }
}

ImportFleet.cs

namespace WebApi.Services.Import.Investments
{
    public class ImportFleet : ImportBase
    {
        public ImportFleet(IInvestmentEntityRepository investmentEntityRepository) : base(investmentEntityRepository)
        {
        }

        public override void Import(IFormFile file, int UserId)
        {
        }
    }
}

ИнвестСервис.cs

namespace WebApi.Services
{
    public interface IInvestmentService
    {
        public void Import(IFormFile file, int UserId, int InvestmentEntityId);        
    }

    public class InvestmentService: IInvestmentService
    {

        public void Import(IFormFile file, int UserId, int InvestmentEntityId)
        {
            IImport importService = null;
            string investmentEntity = ImportBase.determineInvestmentEntityFromCsv(file);

            switch(investmentEntity)
            {
                case "fleet":
                    importService = new ImportFleet();   // problem is here
                    break;
            }

            if (importService != null)
            {
                importService.Import(file, UserId);
            }                          
        }
    }
}

Проблема заключается в следующей строке:

importService = новый ImportKuflink();

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

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

Есть ли способ выполнить вышеизложенное?

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

thewallrus 08.01.2023 17:30

@thewallrus при использовании класса Factory у меня все еще возникает та же проблема при попытке создать экземпляр дочернего класса. Ожидается, что службы, которые я хочу ввести в качестве параметров при вызове new()

Musaffar Patel 08.01.2023 20:17

Внедрение сервисов в качестве параметров (через конструктор) является поведением по умолчанию в системе .NET DI. И услуги могут зависеть друг от друга. Вы должны зарегистрировать службы с правильной областью действия при запуске. Нам нужно будет увидеть ваш запуск или program.cs, чтобы увидеть, как регистрируются службы.

thewallrus 09.01.2023 14:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
70
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Да, конечно, есть способ сделать это. Но я думаю, что контейнер DI, который вы используете (например, из MS), вам здесь не поможет.

Я возился с такой ерундой уже около двух лет и до сих пор занят ею. Два года создания собственного фреймворка IoC.

Обычные микроядра DI/IoC следуют OCP и другим действительно обязательным концепциям и шаблонам. Что я сделал, так это оставил одну маленькую дверь открытой. Не буду утомлять вас подробностями. Фундаментальная идея состоит в том, что класс должен быть снабжен соответствующими атрибутами в коде, а затем он может вызывать микроядро в своем конструкторе (который вызывается простым «var foo = new Barney();»), чтобы позволить сущность может быть изменена так, как будто она была создана микроядром.

Нет (пока) способа подключиться к простому коду new(). Некоторые приветствуют это, некоторые нет. Я здесь с чирлидершами. Почему? Побочные эффекты.

Представьте себе это:

public class SomeNumber
{
    public int SomeValue { get; private set; }

    public SomeNumber()
    {
        SomeValue = 19;
    }
}

Хорошо? Предположим, вы каким-то образом модифицировали процесс new(), тогда другой пользователь вашего кода переходит:

Assert.AreEqual(19, someNumberEntity.SomeNumber);

и этот код выдает исключение, потому что по какой-то причине ваш модифицирующий код установил число 7.

Теперь посмотрите на этот код (из модульного теста):

using System.Reflection;
using Kis.Core.Attributes;

namespace UnitTests_Kis.Core
{
    [KisAware]
    public class KisAwareSimpleClass
    {
        [Property(value: 123)]
        public int ValueToCheck { get; set; } = 0;

        [Property(value: "I am the doctor!")]
        public string Name { get; set; } = "";

        public KisAwareSimpleClass()
        {
            var t = this.GetType();
            var fqtn = t.FullName;
            var ec = new Kis.Core.EntityCreator(Assembly.GetAssembly(t));
            ec.ModifyExistingEntity(fullyQualifiedTypeName: fqtn, existingEntity: this);
        }
    }
}

Чистый код не всегда легко читается, но аспекты/атрибуты повысят осведомленность программиста.

PS: я специально разместил код модульного теста, чтобы показать вам, что происходит.

Укороченная версия:

Microkernel.Modify(this);

Вы можете внедрить фабрику, в которую внедрены службы.

public interface IImportFactory
{
    ImportFleet CreateFleetImporter();
}

public class MyImportFactory : IImportFactory
{
    private readonly IMyDependency1 _dependency1;
    private readonly IMyDependency2 _dependency2;  

    public MyImportFactory(IMyDependency1 dependency1, IMyDependency2 dependency2)
    {
        _dependency1 = dependency1;
        _dependency2 = dependency2;
    }

    public ImportFleet CreateFleetImporter()
    {
        return new ImportFleet(_dependency1, _dependency2);
    }
}

Затем добавьте фабрику в качестве зависимости в свой класс обслуживания.

Ответ принят как подходящий

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

В вашем InvestmentService:

  1. Введите IServiceProvider.
  2. Используйте малоизвестную утилиту ActivatorUtilities, чтобы получить экземпляр вашего объекта с полным внедрением зависимостей.
  3. Убедитесь, что вы утилизировали его правильно, если он реализует IDisposable. Я включил асинхронную версию, если вы используете что-то, что требует IAsyncDisposable.
public class InvestmentService : IInvestmentService
{
    private IServiceProvider _serviceProvider;

    public InvestmentService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        //...
    }

    public Import()
    {
        IImport? importService = null;
        IDisposable? disposable = null;

        var importFleet = ActivatorUtilities.CreateInstance<ImportFleet>(_serviceProvider);
        if (importFleet is IDisposable)
            disposable = importFleet as IDisposable;

        importService = importFleet as IImport;
        // Do whatever you want to do with it

        disposable?.Dispose();
    }

    public async ValueTask ImportAsync()
    {
        IImport? importService = null;
        IDisposable? disposable = null;
        IAsyncDisposable? asyncDisposable = null;

        var importFleet = ActivatorUtilities.CreateInstance<ImportFleet>(_serviceProvider);

        if (importFleet is IDisposable)
            disposable = importFleet as IDisposable;

        if (importFleet is IAsyncDisposable)
            asyncDisposable = importFleet as IAsyncDisposable;

        importService = importFleet as IImport;
        // Do whatever you want to do with it

        disposable?.Dispose();

        if (asyncDisposable is not null)
            await asyncDisposable.DisposeAsync();
    }
}

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

Musaffar Patel 09.01.2023 21:39

Важный. Если вы не удаляете объекты, которые реализуют IDisposable, вы получите утечку памяти в вашем приложении. Однако вам нужно реализовать его только в том случае, если ваши классы реализуют его.

MrC aka Shaun Curtis 09.01.2023 23:14

Спасибо. Любопытно, почему для удаления importFleet требуется отдельный одноразовый объект. Нет ли в importFleet метода, который мог бы избавиться от него (учитывая, что он реализует IDisposable)?

Musaffar Patel 09.01.2023 23:36

Вы не удаляете importFleet, вы удаляете importService, который является экземпляром IImport. Это не реализуется IDisposable. Таким образом, вы захватываете любой созданный вами объект как disposable, если он нуждается в удалении, а затем просто вызываете Dispose для него в конце (если он не нулевой — disposable?).

MrC aka Shaun Curtis 09.01.2023 23:47

Другие вопросы по теме