Пример кода, который у вас есть, это случай Конструктор Injection .
В традиционном коде у вас был бы конструктор без параметров, и в нем вы бы "обновляли" ваши объекты следующим образом:
IEmailService emailService = new EmailService();
Таким образом, ваш код явно контролирует, какая реализация присваивается переменной интерфейса.
В IoC с использованием инжекции в конструктор управление инвертируется, что означает, что контейнер "управляет шиной" и создает ваш объект TemplateEmailService. Когда он собирается его создать, контейнер просматривает параметры вашего конструктора (IEmailService, ITemplateRenderer и т. Д.) И передает эти объекты в ваш класс для использования.
Контейнер IoC можно настроить так, чтобы интерфейс A выполнялся реализацией B (или C) явно. У каждого есть способ сделать это. Или он может сделать это по соглашению (IFoo выполняется Foo), или даже атрибуты в классах, что угодно.
Итак, чтобы ответить на ваш вопрос - вы можете явно определить, какие реализации используются для выполнения определенных интерфейсов. Нужно прочитать документацию по контейнеру IoC, чтобы узнать, как это сделать.
Еще одна вещь - «когда вы так кодируете», вам технически не нужно использовать контейнер IoC. Фактически, ваш класс не должен иметь прямой ссылки на контейнер - это максимизирует возможность повторного использования, а также позволяет легко тестировать. Таким образом, вы бы подключили интерфейсы к классам реализации в другом месте.