Skip to content

Commit d3082bd

Browse files
Fix nested scope root propagation and add singleton regressions
1 parent f9c834e commit d3082bd

2 files changed

Lines changed: 149 additions & 2 deletions

File tree

src/Pure.DI.Core/Core/Code/Parts/ScopeConstructorBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ public CompositionCode Build(CompositionCode composition)
5959
code.AppendLine($"if ({Names.ObjectTypeName}.ReferenceEquals({Names.ChildScopeArgName}, null)) throw new {Names.SystemNamespace}ArgumentNullException(nameof({Names.ChildScopeArgName}));");
6060
if (composition.Singletons.Length > 0)
6161
{
62-
code.AppendLine($"{destination}{Names.RootFieldName} = {Names.ParentScopeArgName};");
62+
code.AppendLine($"{destination}{Names.RootFieldName} = {Names.ParentScopeArgName}.{Names.RootFieldName} ?? {Names.ParentScopeArgName};");
6363
}
6464
}
6565
else
6666
{
6767
if (composition.Singletons.Length > 0)
6868
{
69-
code.AppendLine($"{destination}{Names.RootFieldName} = {source}{Names.RootFieldName};");
69+
code.AppendLine($"{destination}{Names.RootFieldName} = {source}{Names.RootFieldName} ?? {Names.ParentScopeArgName};");
7070
}
7171
}
7272

tests/Pure.DI.IntegrationTests/SetupContextTests.cs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,153 @@ public static void Main()
446446
result.StdOut.ShouldBe(["41", "41", "True", "True"], result);
447447
}
448448

449+
[Fact]
450+
public async Task ShouldShareSingletonAcrossNestedScopes()
451+
{
452+
// Given
453+
454+
// When
455+
var result = await """
456+
using System;
457+
using Pure.DI;
458+
459+
namespace Sample
460+
{
461+
interface ISingleton
462+
{
463+
Guid Id { get; }
464+
}
465+
466+
sealed class Singleton : ISingleton
467+
{
468+
public Guid Id { get; } = Guid.NewGuid();
469+
}
470+
471+
interface IService
472+
{
473+
ISingleton Singleton { get; }
474+
}
475+
476+
sealed class Service : IService
477+
{
478+
public Service(ISingleton singleton)
479+
{
480+
Singleton = singleton;
481+
}
482+
483+
public ISingleton Singleton { get; }
484+
}
485+
486+
partial class Composition
487+
{
488+
void Setup() => DI.Setup(nameof(Composition))
489+
.Hint(Hint.ScopeMethodName, "SetupScope")
490+
.Bind<ISingleton>().As(Lifetime.Singleton).To<Singleton>()
491+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
492+
.Root<IService>("Service");
493+
}
494+
495+
class Program
496+
{
497+
static void Main()
498+
{
499+
var composition = new Composition();
500+
var rootSingleton = composition.Service.Singleton;
501+
var scope1 = Composition.SetupScope(composition, new Composition());
502+
var scope1Singleton = scope1.Service.Singleton;
503+
var scope2 = Composition.SetupScope(scope1, new Composition());
504+
var scope2Singleton = scope2.Service.Singleton;
505+
Console.WriteLine(rootSingleton == scope1Singleton);
506+
Console.WriteLine(scope1Singleton == scope2Singleton);
507+
Console.WriteLine(rootSingleton == scope2Singleton);
508+
}
509+
}
510+
}
511+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
512+
513+
// Then
514+
result.Success.ShouldBeTrue(result);
515+
result.Errors.Count.ShouldBe(0, result);
516+
result.Warnings.Count.ShouldBe(0, result);
517+
result.StdOut.ShouldBe(["True", "True", "True"], result);
518+
}
519+
520+
[Fact]
521+
public async Task ShouldKeepSingletonAvailableForNestedScopeAfterIntermediateScopeDispose()
522+
{
523+
// Given
524+
525+
// When
526+
var result = await """
527+
using System;
528+
using Pure.DI;
529+
530+
namespace Sample
531+
{
532+
interface ISingleton
533+
{
534+
Guid Id { get; }
535+
}
536+
537+
sealed class Singleton : ISingleton, IDisposable
538+
{
539+
public Guid Id { get; } = Guid.NewGuid();
540+
541+
public bool IsDisposed { get; private set; }
542+
543+
public void Dispose() => IsDisposed = true;
544+
}
545+
546+
interface IService
547+
{
548+
ISingleton Singleton { get; }
549+
}
550+
551+
sealed class Service : IService
552+
{
553+
public Service(ISingleton singleton)
554+
{
555+
Singleton = singleton;
556+
}
557+
558+
public ISingleton Singleton { get; }
559+
}
560+
561+
partial class Composition
562+
{
563+
void Setup() => DI.Setup(nameof(Composition))
564+
.Hint(Hint.ScopeMethodName, "SetupScope")
565+
.Bind<ISingleton>().As(Lifetime.Singleton).To<Singleton>()
566+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
567+
.Root<IService>("Service");
568+
}
569+
570+
class Program
571+
{
572+
static void Main()
573+
{
574+
var composition = new Composition();
575+
var scope1 = Composition.SetupScope(composition, new Composition());
576+
var scope1Singleton = (Singleton)scope1.Service.Singleton;
577+
var scope2 = Composition.SetupScope(scope1, new Composition());
578+
var scope2SingletonBeforeDispose = (Singleton)scope2.Service.Singleton;
579+
scope1.Dispose();
580+
var scope2SingletonAfterDispose = (Singleton)scope2.Service.Singleton;
581+
Console.WriteLine(scope1Singleton == scope2SingletonBeforeDispose);
582+
Console.WriteLine(scope2SingletonBeforeDispose == scope2SingletonAfterDispose);
583+
Console.WriteLine(scope2SingletonAfterDispose.IsDisposed);
584+
}
585+
}
586+
}
587+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
588+
589+
// Then
590+
result.Success.ShouldBeTrue(result);
591+
result.Errors.Count.ShouldBe(0, result);
592+
result.Warnings.Count.ShouldBe(0, result);
593+
result.StdOut.ShouldBe(["True", "True", "False"], result);
594+
}
595+
449596
[Fact]
450597
public async Task ShouldSupportRootArgumentWithSimpleFactory()
451598
{

0 commit comments

Comments
 (0)