Получите открытый универсальный сервис для внедрения зависимостей Microsoft - PullRequest
0 голосов
/ 28 ноября 2018

Предположим, у нас есть следующие сервисы:

interface IService { }
interface IService<T> : IService {
    T Get();
}

В ASP.Net-Core, после того, как мы зарегистрировали несколько реализаций с разными T, мы можем получить все зарегистрированные сервисы, такие как:

IEnumerable<IService> services = serviceProvider.GetServices<IService>();

Теперь, потому что мне нужен доступ к параметру универсального типа из другого интерфейса, который не является опцией. Как я могу получить все реализации IService<T> без потери универсального типа? Что-то вроде:

IEnumerable<IService<T>> services = serviceProvider.GetServices<IService<T>>();
foreach (var s in services) {
    Method(s);
}

// Here we have a generic method I don't have control over.
// I want to call the method for each `T` registered in DI
void Method<T>(IService<T> service) {
    Type t = typeof(T); // This here will resolve to the actual type, different in each call. Not object or whatever less derived.
}

И все это должно иметь несколько приличную производительность.

Ответы [ 2 ]

0 голосов
/ 24 декабря 2018

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

Отражение - это тип позднего связывания, но есть лучшее решение для этого: dynamic Примером ответа будет:

IEnumerable<IService> services = serviceProvider.GetServices<IService>();
foreach (var s in services) {
    Method((dynamic)s);
}

void Method<T>(IService<T> service) {
    // This here will resolve to the actual type, different in each call. Not object or whatever less derived.
    Type t = typeof(T);
}

Приведение к dynamic будетотложить привязку метода до времени выполнения, когда фактический тип s известен.Затем он будет искать наиболее подходящую перегрузку (если ее нет, будет выдано исключение).Этот подход имеет некоторые преимущества использования отражения:

  • Нам не нужно заботиться о кэшировании.DLR (Dynamic Language Runtime) будет обрабатывать его для нас.
  • Мы связываемся поздно, но получаем как можно больше статического анализа.Например, все другие типы аргументов проверяются и могут привести к ошибке времени компиляции, если они недействительны.
  • Это короче и проще для написания.

Вы можете прочитать отличный подробный постоб этом подходе и его сравнении с отражением здесь .

0 голосов
/ 28 ноября 2018

Есть 2 варианта, которые я могу придумать:

  1. Введите IService и отфильтруйте несовместимые типы:

    serviceProvider.GetServices<IService>().OfType<IService<T>>();
    
  2. Сделайте дубликаты регистрации:

    services.AddScoped<IService, FooService>();
    services.AddScoped<IService, BarService1>();
    services.AddScoped<IService, BarService2>();
    services.AddScoped<IService<Foo>, FooService>();
    services.AddScoped<IService<Bar>, BarService1>();
    services.AddScoped<IService<Bar>, BarService2>();
    
    serviceProvider.GetServices<IService<Bar>>(); // returns 2 services
    serviceProvider.GetServices<IService>(); // returns 3 services
    

    Обратите внимание, однако, что вы должны быть осторожны с этими дублирующимися регистрациями, чтобы не попасть в ловушку Torn Lifestyles .Это может произойти, когда служба зарегистрирована как Scoped или Singleton.Чтобы бороться с этим, вам нужно изменить вышеуказанные регистрации следующим образом:

    services.AddScoped<FooService>();
    services.AddScoped<BarService1>();
    services.AddScoped<BarService2>();
    
    services.AddScoped<IService>(c => c.GetRequiredService<FooService>());
    services.AddScoped<IService>(c => c.GetRequiredService<BarService1>());
    services.AddScoped<IService>(c => c.GetRequiredService<BarService2>());
    services.AddScoped<IService<Foo>>(c => c.GetRequiredService<FooService>());
    services.AddScoped<IService<Bar>>(c => c.GetRequiredService<BarService1>());
    services.AddScoped<IService<Bar>>(c => c.GetRequiredService<BarService2>());
    

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...