Реализация AddSingleton<T1, T2>
выглядит так:
public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
return services.AddSingleton(typeof(TService), typeof(TImplementation));
}
Как вы можете видеть, она буквально вызывает другую перегрузку AddSingleton
, которая вместо этого передает два типа. Таким образом, семантически не имеет значения, выполняете ли вы AddSingleton<IDb, Db>()
или AddSingleton(typeof(IDb), typeof(Db))
. Оба вызова приведут к одному и тому же результату.
Причина общей c перегрузки в том, что она кажется намного лучше. Методы Generi c предпочтительнее передаваемых типов, потому что вы можете просто написать их проще. Таким образом, вы с большей вероятностью увидите использование generi c.
Кроме того, есть преимущество, заключающееся в том, что вы можете добавлять ограничения к аргументам типа generi c, которые могут добавлять некоторые проверки времени компиляции. В данном конкретном случае ограничения выглядят следующим образом:
where TService : class
where TImplementation : class, TService
Помимо того, что оба типа должны быть ссылочными типами, существует дополнительное требование, которое TImplementation
наследует от TService
. Это гарантирует, что вы действительно можете использовать экземпляры типа TImplementation
в местах, где ожидается TService
. Это также идея принципа замены Лискова . При наличии ограничения типа эта проверка проверяется во время компиляции , поэтому вы можете быть уверены, что это будет работать во время выполнения, поскольку это не гарантируется, если вы используете другую перегрузку.
Излишне говорить, что AddTransient<>
и AddScoped<>
работают одинаково с соответствующими не-родовыми c перегрузками.