英文:
C#: Interface Implementation behaves differently for Singleton
问题
以下是翻译的内容:
1st statement:
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
第一句:
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
2nd statement:
public Guid Id => Guid.NewGuid();
第二句:
public Guid Id => Guid.NewGuid();
IReportServiceLifetime.cs:
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
IReportServiceLifetime.cs:
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
IExampleSingletonService.cs:
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
IExampleSingletonService.cs:
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
ExampleSingletonService.cs:
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
// public Guid Id => Guid.NewGuid();
}
ExampleSingletonService.cs:
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
// public Guid Id => Guid.NewGuid();
}
ServiceLifetimeReporter.cs:
internal sealed class ServiceLifetimeReporter
{
private readonly IExampleSingletonService _singletonService;
public ServiceLifetimeReporter(IExampleSingletonService singletonService) =>
(_singletonService) =
(singletonService);
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(_singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
ServiceLifetimeReporter.cs:
internal sealed class ServiceLifetimeReporter
{
private readonly IExampleSingletonService _singletonService;
public ServiceLifetimeReporter(IExampleSingletonService singletonService) =>
(_singletonService) =
(singletonService);
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(_singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
Program.cs:
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
services.AddTransient<ServiceLifetimeReporter>();
})
.Build();
ServiceLifetime(host.Services, "Lifetime 1");
ServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
Program.cs:
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
services.AddTransient<ServiceLifetimeReporter>();
})
.Build();
ServiceLifetime(host.Services, "Lifetime 1");
ServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
英文:
What is the difference between below two implementations?
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid(); //1st statement
// public Guid Id => Guid.NewGuid(); //2nd statement
The below lines of code behaving differently for Singleton
For the above case (2nd statement is commented out) the code works correctly. One can find out by looking at same Guid for all the requests.
But if one runs by making following changes. The Guid is different for all the requests.
//Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid(); //1st statement
public Guid Id => Guid.NewGuid(); //2nd statement
IReportServiceLifetime.cs
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
IExampleSingletonService.cs
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
ExampleSingletonService.cs
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
// public Guid Id => Guid.NewGuid();
}
ServiceLifetimeReporter.cs
internal sealed class ServiceLifetimeReporter
{
private readonly IExampleSingletonService _singletonService;
public ServiceLifetimeReporter(IExampleSingletonService singletonService) =>
(_singletonService) =
(singletonService);
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(_singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
Program.cs
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
services.AddTransient<ServiceLifetimeReporter>();
})
.Build();
ServiceLifetime(host.Services, "Lifetime 1");
ServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
答案1
得分: 2
你所写的是一种通常被称为“语法糖”的属性简化语法。C#编译器将其扩展为以下等效代码:
private readonly Guid _Id1_BackingField = Guid.NewGuid();
public Guid Id1
{
get
{
return _Id1_BackingField;
}
}
public Guid Id2
{
get
{
return Guid.NewGuid();
}
}
但是,这个后备字段是隐藏的。
Id
的创建表达式被转换为只读后备字段的字段初始化程序。
而对于 Id2
,创建表达式用于返回语句中,显然每次读取属性时都会调用。
您可以在 SharpLab.io 上进行测试。C#编译器用来隐藏后备字段的技巧是使用在C#中无效的标识符(<Id1>k__BackingField
)。
请注意,C#编译器生成IL代码。此标识符在IL中是有效的(您可以通过右侧面板的下拉框切换到IL视图)。
英文:
What you wrote is a simplified syntax for properties that is often called "syntactic sugar". The C# compiler expands this:
public Guid Id1 { get; } = Guid.NewGuid();
public Guid Id2 => Guid.NewGuid();
... to a code equivalent to:
private readonly Guid _Id1_BackingField = Guid.NewGuid();
public Guid Id1
{
get
{
return _Id1_BackingField;
}
}
public Guid Id2
{
get
{
return Guid.NewGuid();
}
}
The backing field is however hidden.
The creation expression of Id
is converted to a field initializer of a read-only backing field.
Whereas for Id2
the creation expression is used in a return-statement which is obviously called every time you are reading the property.
You can test this on SharpLab.io. The trick the C# compiler uses to hide the backing field is to use an identifier which is invalid in C# (<Id1>k__BackingField
).
Note that the C# compiler generates IL code. This identifier is valid in IL. (You can switch to IL view through the drop-down-box on the right panel.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论