Skip to content

Commit e281f4c

Browse files
3
1 parent 51b2ae3 commit e281f4c

16 files changed

Lines changed: 539 additions & 61 deletions

src/Pure.DI.Core/Components/Api.g.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,18 @@ internal enum Hint
11891189
/// </example>
11901190
/// </summary>
11911191
LightweightAnonymousRoot,
1192+
1193+
/// <summary>
1194+
/// Specifies the strategy used to represent and create scopes. Default: <c>CompositionInstance</c>.
1195+
/// <example>
1196+
/// <code>
1197+
/// DI.Setup("Composition")
1198+
/// .Hint(Hint.ScopeStrategy, ScopeStrategy.StateObject)
1199+
/// .Bind&lt;IDependency&gt;().As(Lifetime.Scoped).To&lt;Dependency&gt;();
1200+
/// </code>
1201+
/// </example>
1202+
/// </summary>
1203+
ScopeStrategy,
11921204
}
11931205

11941206
/// <summary>
@@ -1512,6 +1524,22 @@ internal enum SetupContextKind
15121524
Reference
15131525
}
15141526

1527+
/// <summary>
1528+
/// Specifies how scoped lifetime should be represented in generated code.
1529+
/// </summary>
1530+
internal enum ScopeStrategy
1531+
{
1532+
/// <summary>
1533+
/// A scope is represented by another composition instance.
1534+
/// </summary>
1535+
CompositionInstance,
1536+
1537+
/// <summary>
1538+
/// A scope is represented by a dedicated state object.
1539+
/// </summary>
1540+
StateObject
1541+
}
1542+
15151543
/// <summary>
15161544
/// Specifies flags for composition root members, including access level, modifiers, and representation.
15171545
/// Flags can be combined.

src/Pure.DI.Core/Components/Api.g.tt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,18 @@ namespace Pure.DI
11941194
/// </example>
11951195
/// </summary>
11961196
LightweightAnonymousRoot,
1197+
1198+
/// <summary>
1199+
/// Specifies the strategy used to represent and create scopes. Default: <c>CompositionInstance</c>.
1200+
/// <example>
1201+
/// <code>
1202+
/// DI.Setup("Composition")
1203+
/// .Hint(Hint.ScopeStrategy, ScopeStrategy.StateObject)
1204+
/// .Bind&lt;IDependency&gt;().As(Lifetime.Scoped).To&lt;Dependency&gt;();
1205+
/// </code>
1206+
/// </example>
1207+
/// </summary>
1208+
ScopeStrategy,
11971209
}
11981210

11991211
/// <summary>
@@ -1517,6 +1529,22 @@ namespace Pure.DI
15171529
Reference
15181530
}
15191531

1532+
/// <summary>
1533+
/// Specifies how scoped lifetime should be represented in generated code.
1534+
/// </summary>
1535+
internal enum ScopeStrategy
1536+
{
1537+
/// <summary>
1538+
/// A scope is represented by another composition instance.
1539+
/// </summary>
1540+
CompositionInstance,
1541+
1542+
/// <summary>
1543+
/// A scope is represented by a dedicated state object.
1544+
/// </summary>
1545+
StateObject
1546+
}
1547+
15201548
/// <summary>
15211549
/// Specifies flags for composition root members, including access level, modifiers, and representation.
15221550
/// Flags can be combined.

src/Pure.DI.Core/Core/Code/Constructors.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@ from edge in entry.Edges
2929
from node in ImmutableArray.Create(edge.Source, edge.Target)
3030
where node.Arg is { Source.Kind: ArgKind.Composition }
3131
where bindingsRegistry.IsRegistered(graph.Source, node.BindingId)
32-
select node).Any() || HasSetupContextArgs(graph) || IsScopeEnabled(graph);
32+
select node).Any() || HasSetupContextArgs(graph) || HasScopedLifetimes(graph);
3333

34-
private bool IsScopeEnabled(DependencyGraph graph) => (
34+
private bool HasScopedLifetimes(DependencyGraph graph) => (
3535
from node in graph.Graph.Vertices
3636
where node.ActualLifetime == Lifetime.Scoped
3737
where bindingsRegistry.IsRegistered(graph.Source, node.BindingId)
3838
select node).Any();
39+
40+
private bool IsScopeEnabled(DependencyGraph graph) =>
41+
graph.Source.Hints.ScopeStrategy == ScopeStrategy.CompositionInstance
42+
&& HasScopedLifetimes(graph);
3943
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public CompositionCode Build(CompositionCode composition)
2020
var code = composition.Code;
2121
var membersCounter = composition.MembersCount;
2222
var hints = composition.Source.Source.Hints;
23+
var scopeStrategy = hints.ScopeStrategy;
24+
var hasStateObjectScope = scopeStrategy == ScopeStrategy.StateObject;
25+
var hasScopedLifetimes = composition.Singletons.Any(i => i.Node.ActualLifetime == Lifetime.Scoped);
26+
var hasSingletonLifetimes = composition.Singletons.Any(i => i.Node.ActualLifetime == Lifetime.Singleton);
27+
var rootDisposablesCount = hasStateObjectScope
28+
? composition.TotalDisposablesCount - composition.DisposablesScopedCount
29+
: composition.TotalDisposablesCount;
2330
var isCommentsEnabled = hints.IsCommentsEnabled;
2431
if (isCommentsEnabled)
2532
{
@@ -33,7 +40,7 @@ public CompositionCode Build(CompositionCode composition)
3340
code.AppendLine($"public {ctorName}()");
3441
using (code.CreateBlock())
3542
{
36-
if (composition.Singletons.Length > 0)
43+
if (hasSingletonLifetimes)
3744
{
3845
code.AppendLine($"{Names.RootFieldName} = this;");
3946
}
@@ -47,9 +54,14 @@ public CompositionCode Build(CompositionCode composition)
4754
code.AppendLine(new Line(int.MinValue, "#endif"));
4855
}
4956

50-
if (composition.TotalDisposablesCount > 0)
57+
if (rootDisposablesCount > 0)
5158
{
52-
code.AppendLine($"{Names.DisposablesFieldName} = new object[{composition.TotalDisposablesCount.ToString()}];");
59+
code.AppendLine($"{Names.DisposablesFieldName} = new object[{rootDisposablesCount.ToString()}];");
60+
}
61+
62+
if (hasStateObjectScope && hasScopedLifetimes)
63+
{
64+
code.AppendLine($"{Names.RootScopeFieldName} = new ScopeHandle(this, isRootScope: true);");
5365
}
5466
}
5567

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

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ public CompositionCode Build(CompositionCode composition)
2323
var hasDisposable = composition.DisposablesCount > 0;
2424
var hasAsyncDisposable = composition.AsyncDisposableCount > 0;
2525
var hints = composition.Source.Source.Hints;
26+
var hasStateObjectScope = hints.ScopeStrategy == ScopeStrategy.StateObject;
27+
var hasScopedLifetimes = composition.Singletons.Any(i => i.Node.ActualLifetime == Lifetime.Scoped);
28+
var rootDisposablesCount = hasStateObjectScope
29+
? composition.TotalDisposablesCount - composition.DisposablesScopedCount
30+
: composition.TotalDisposablesCount;
31+
var singletonFieldsToReset = hasStateObjectScope
32+
? composition.Singletons.Where(i => i.Node.ActualLifetime == Lifetime.Singleton).ToImmutableArray()
33+
: composition.Singletons;
2634
var isCommentsEnabled = hints.IsCommentsEnabled;
2735
if (isCommentsEnabled)
2836
{
@@ -34,27 +42,30 @@ public CompositionCode Build(CompositionCode composition)
3442
code.AppendLine($"{composition.Source.Source.Hints.DisposeMethodModifiers} void Dispose()");
3543
using (code.CreateBlock())
3644
{
37-
AddSyncPart(composition, code, false);
38-
code.AppendLine();
39-
code.AppendLine("while (disposeIndex-- > 0)");
40-
using (code.CreateBlock())
45+
AddSyncPart(composition, code, false, rootDisposablesCount, hasStateObjectScope, hasScopedLifetimes, singletonFieldsToReset);
46+
if (rootDisposablesCount > 0)
4147
{
42-
code.AppendLine("switch (disposables[disposeIndex])");
48+
code.AppendLine();
49+
code.AppendLine("while (disposeIndex-- > 0)");
4350
using (code.CreateBlock())
4451
{
45-
if (hasDisposable)
46-
{
47-
AddDisposePart(code);
48-
}
49-
50-
if (hasAsyncDisposable)
52+
code.AppendLine("switch (disposables[disposeIndex])");
53+
using (code.CreateBlock())
5154
{
5255
if (hasDisposable)
5356
{
54-
code.AppendLine();
57+
AddDisposePart(code);
5558
}
5659

57-
AddDisposeAsyncPart(code, false);
60+
if (hasAsyncDisposable)
61+
{
62+
if (hasDisposable)
63+
{
64+
code.AppendLine();
65+
}
66+
67+
AddDisposeAsyncPart(code, false);
68+
}
5869
}
5970
}
6071
}
@@ -86,27 +97,30 @@ public CompositionCode Build(CompositionCode composition)
8697
code.AppendLine($"{composition.Source.Source.Hints.DisposeAsyncMethodModifiers} async {Names.ValueTaskTypeName} DisposeAsync()");
8798
using (code.CreateBlock())
8899
{
89-
AddSyncPart(composition, code, true);
90-
code.AppendLine();
91-
code.AppendLine("while (disposeIndex-- > 0)");
92-
using (code.CreateBlock())
100+
AddSyncPart(composition, code, true, rootDisposablesCount, hasStateObjectScope, hasScopedLifetimes, singletonFieldsToReset);
101+
if (rootDisposablesCount > 0)
93102
{
94-
code.AppendLine("switch (disposables[disposeIndex])");
103+
code.AppendLine();
104+
code.AppendLine("while (disposeIndex-- > 0)");
95105
using (code.CreateBlock())
96106
{
97-
if (hasAsyncDisposable)
98-
{
99-
AddDisposeAsyncPart(code, true);
100-
}
101-
102-
if (hasDisposable)
107+
code.AppendLine("switch (disposables[disposeIndex])");
108+
using (code.CreateBlock())
103109
{
104110
if (hasAsyncDisposable)
105111
{
106-
code.AppendLine();
112+
AddDisposeAsyncPart(code, true);
107113
}
108114

109-
AddDisposePart(code);
115+
if (hasDisposable)
116+
{
117+
if (hasAsyncDisposable)
118+
{
119+
code.AppendLine();
120+
}
121+
122+
AddDisposePart(code);
123+
}
110124
}
111125
}
112126
}
@@ -182,10 +196,26 @@ private static void AddDisposePart(Lines code)
182196
}
183197
}
184198

185-
private void AddSyncPart(CompositionCode composition, Lines code, bool isAsync)
199+
private void AddSyncPart(
200+
CompositionCode composition,
201+
Lines code,
202+
bool isAsync,
203+
int rootDisposablesCount,
204+
bool hasStateObjectScope,
205+
bool hasScopedLifetimes,
206+
ImmutableArray<VarDeclaration> singletonFieldsToReset)
186207
{
187-
code.AppendLine("int disposeIndex;");
188-
code.AppendLine("object[] disposables;");
208+
if (hasStateObjectScope && hasScopedLifetimes)
209+
{
210+
code.AppendLine($"{Names.RootScopeFieldName}.Dispose();");
211+
}
212+
213+
if (rootDisposablesCount > 0)
214+
{
215+
code.AppendLine("int disposeIndex;");
216+
code.AppendLine("object[] disposables;");
217+
}
218+
189219
var isLockRequired = composition.IsLockRequired(locks);
190220
if (isLockRequired)
191221
{
@@ -194,11 +224,15 @@ private void AddSyncPart(CompositionCode composition, Lines code, bool isAsync)
194224
code.IncIndent();
195225
}
196226

197-
code.AppendLine($"disposeIndex = {Names.DisposeIndexFieldName};");
198-
code.AppendLine($"{Names.DisposeIndexFieldName} = 0;");
199-
code.AppendLine($"disposables = {Names.DisposablesFieldName};");
200-
code.AppendLine($"{Names.DisposablesFieldName} = new object[{composition.TotalDisposablesCount.ToString()}];");
201-
foreach (var singletonField in composition.Singletons)
227+
if (rootDisposablesCount > 0)
228+
{
229+
code.AppendLine($"disposeIndex = {Names.DisposeIndexFieldName};");
230+
code.AppendLine($"{Names.DisposeIndexFieldName} = 0;");
231+
code.AppendLine($"disposables = {Names.DisposablesFieldName};");
232+
code.AppendLine($"{Names.DisposablesFieldName} = new object[{rootDisposablesCount.ToString()}];");
233+
}
234+
235+
foreach (var singletonField in singletonFieldsToReset)
202236
{
203237
if (singletonField.InstanceType.IsValueType)
204238
{
@@ -218,4 +252,4 @@ private void AddSyncPart(CompositionCode composition, Lines code, bool isAsync)
218252
code.AppendLine(BlockFinish);
219253
}
220254
}
221-
}
255+
}

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,34 @@ public CompositionCode Build(CompositionCode composition)
1717
var membersCounter = composition.MembersCount;
1818
var compilation = composition.Compilation;
1919
var nullable = compilation.Options.NullableContextOptions == NullableContextOptions.Disable ? "" : "?";
20+
var scopeStrategy = composition.Source.Source.Hints.ScopeStrategy;
21+
var hasStateObjectScope = scopeStrategy == ScopeStrategy.StateObject;
22+
var hasScopedLifetimes = composition.Singletons.Any(i => i.Node.ActualLifetime == Lifetime.Scoped);
23+
var singletonFields = hasStateObjectScope
24+
? composition.Singletons.Where(i => i.Node.ActualLifetime == Lifetime.Singleton).ToImmutableArray()
25+
: composition.Singletons;
26+
var rootDisposablesCount = hasStateObjectScope
27+
? composition.TotalDisposablesCount - composition.DisposablesScopedCount
28+
: composition.TotalDisposablesCount;
2029

2130
var isAnyConstructorEnabled = constructors.IsEnabled(composition.Source);
22-
if (isAnyConstructorEnabled && composition.Singletons.Length > 0)
31+
if (isAnyConstructorEnabled && singletonFields.Length > 0)
2332
{
2433
// _parent filed
2534
code.AppendLine($"[{Names.NonSerializedAttributeTypeName}] private readonly {composition.Source.Source.Name.ClassName} {Names.RootFieldName};");
2635
membersCounter++;
2736
}
2837

38+
if (hasStateObjectScope && hasScopedLifetimes)
39+
{
40+
code.AppendLine($"[{Names.NonSerializedAttributeTypeName}] private readonly ScopeHandle {Names.RootScopeFieldName};");
41+
membersCounter++;
42+
43+
code.AppendLine("[ThreadStatic]");
44+
code.AppendLine($"private static ScopeHandle{nullable} {Names.CurrentScopeFieldName};");
45+
membersCounter++;
46+
}
47+
2948
if (composition.IsLockRequired(locks))
3049
{
3150
// _lock field
@@ -37,10 +56,10 @@ public CompositionCode Build(CompositionCode composition)
3756
membersCounter++;
3857
}
3958

40-
if (composition.TotalDisposablesCount > 0)
59+
if (rootDisposablesCount > 0)
4160
{
4261
// _disposables field
43-
code.AppendLine($"[{Names.NonSerializedAttributeTypeName}] private object[] {Names.DisposablesFieldName}{(isAnyConstructorEnabled ? "" : $" = new object[{composition.TotalDisposablesCount.ToString()}]")};");
62+
code.AppendLine($"[{Names.NonSerializedAttributeTypeName}] private object[] {Names.DisposablesFieldName}{(isAnyConstructorEnabled ? "" : $" = new object[{rootDisposablesCount.ToString()}]")};");
4463
membersCounter++;
4564

4665
// _disposeIndex field
@@ -49,10 +68,10 @@ public CompositionCode Build(CompositionCode composition)
4968
}
5069

5170
// Singleton fields
52-
if (composition.Singletons.Length > 0)
71+
if (singletonFields.Length > 0)
5372
{
5473
code.AppendLine();
55-
foreach (var singletonField in composition.Singletons)
74+
foreach (var singletonField in singletonFields)
5675
{
5776
if (singletonField.InstanceType.IsValueType)
5877
{
@@ -72,4 +91,4 @@ public CompositionCode Build(CompositionCode composition)
7291

7392
return composition with { MembersCount = membersCounter };
7493
}
75-
}
94+
}

0 commit comments

Comments
 (0)