Обслуживание:
public interface IMessageWriter
{
void Write(string message);
}
public class ConsoleMessageWriter:IMessageWriter, IDisposable
{
public void Dispose()
{
Console.WriteLine($"The {GetType().Name} instanse is disposed.");
}
public void Write(string message)
{
Console.WriteLine(message);
}
}
Теперь я использую его в области видимости:
using DI_Sandbox.Models;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services
.AddScoped<ConsoleMessageWriter>()
.AddScoped<IMessageWriter>(provider => provider.GetRequiredService<ConsoleMessageWriter>());
var container = services.BuildServiceProvider(validateScopes:true);
using (var serviceScope = container.CreateScope())
{
var messageWriter1 = serviceScope.ServiceProvider.GetRequiredService<IMessageWriter>();
var messageWriter2 = serviceScope.ServiceProvider.GetRequiredService<IMessageWriter>();
Console.WriteLine($"messageWriter1 == messageWriter2: {messageWriter1 == messageWriter2}");
messageWriter1.Write("Hello DI!");
}
Console.Write("THE END");
Выход:
messageWriter1 == messageWriter2: True
Hello DI!
The ConsoleMessageWriter instanse is disposed.
The ConsoleMessageWriter instanse is disposed.
THE END
Почему служба с ограниченной областью действия была удалена дважды? Как это исправить? Мне нужно удалить службу один раз.
Вы сделали две регистрации. Один для ConsoleMessageWriter
и один для IMessageWriter
:
services.AddScoped<ConsoleMessageWriter>()
services.AddScoped<IMessageWriter>(p =>
p.GetRequiredService<ConsoleMessageWriter>());
Хотя регистрация для IMessageWriter
вызывает обратную связь с регистрацией ConsoleMessageWriter
, MS.DI не знает об этом. Однако он знает, что обе регистрации приводят к созданию объекта IDisposable
. Это происходит даже несмотря на то, что IMessageWriter
не реализуется IDisposable
. Проверка выполняется во время принятия решения. Каждая регистрация отслеживает свой возвращенный объект, не зная, что это тот же самый объект.
Вы можете считать это ошибкой дизайна, потому что, по крайней мере, MS.DI мог бы убедиться, что список одноразовых экземпляров дедуплицирован, прежде чем вызывать Dispose
на них. Но это, вероятно, компромисс с производительностью, потому что:
Если бы вы выполнили регистрацию следующим образом, это привело бы к проблеме Torn Lifestyle, приводящей к появлению двух экземпляров ConsoleMessageWriter
в одной области:
// Incorrect registration. Leads to a Torn Lifestyle
services.AddScoped<ConsoleMessageWriter>()
services.AddScoped<IMessageWriter, ConsoleMessageWriter>();
Дизайн MS.DI прост (некоторые скажут наивный), и единственный способ предотвратить появление Torn Lifestyle компонентов - это выполнить регистрацию, которую вы сделали в своем вопросе:
services.AddScoped<ConsoleMessageWriter>()
services.AddScoped<IMessageWriter>(p =>
p.GetRequiredService<ConsoleMessageWriter>());
Другими словами: у вас есть регистрация, которая выполняет обратный вызов в контейнер для разрешения фактического компонента, который вы ищете.
Более зрелые DI-контейнеры имеют более сложные способы предотвращения разрыва образа жизни. На ум приходят два: Unity и Simple Injector. Например, Simple Injector позволит вам выполнить регистрацию следующим образом, сохраняя при этом один экземпляр ConsoleMessageWriter
для каждой области:
// Registrations using Simple Injector. No Torn Lifestyle here.
container.Register<ConsoleMessageWriter>(Lifestyle.Scoped);
container.Register<IMessageWriter, ConsoleMessageWriter>(Lifestyle.Scoped);
Unity IoC ведет себя аналогично, в то время как для других DI-контейнеров может потребоваться конструкция, аналогичная той, что вам нужна для MS.DI.
Если дубликат удаления вызывает проблему, способ обойти это — создать одноразовый класс-оболочку для интерфейса IMessageWriter
, например:
public sealed class NonDisposableMessageWriterWrapper : IMessageWriter
{
private readonly IMessageWriter decoratee;
public NonDisposableMessageWriterWrapper(IMessageWriter decoratee) =>
this.decoratee = decoratee;
public void Write(string message) => this.decoratee.Write(message);
}
И измените регистрации на следующие:
services.AddScoped<ConsoleMessageWriter>()
services.AddScoped<IMessageWriter>(p =>
new NonDisposableMessageWriterWrapper(
p.GetRequiredService<ConsoleMessageWriter>()));
Таким образом ConsoleMessageWriter
больше не утилизируется дважды.
Но стоит ли эта дополнительная сложность того? Возможно нет.
Хороший! Никогда раньше не слышал о Torn Lifestyle, столкнусь ли я с той же проблемой, используя MS.DI .AddDbContext<C>() и .AddScoped<IDbContext, C>()?
Да, ты бы сделал это. Это может вызвать ту же проблему: вы получите два экземпляра C
в одной области.
Если ваш
Dispose
идемпотентен (как рекомендуется здесь ), то это раздражает, но, конечно, не блокирует.... «Чтобы обеспечить правильную очистку ресурсов, метод Dispose должен быть идемпотентным, чтобы он вызывается несколько раз без возникновения исключения. Кроме того, последующие вызовы Dispose не должны ничего делать».