Skip to content

Commit 3a309e5

Browse files
Implement scoped lifetime disposal with automatic resource cleanup via Scope object
1 parent aad7097 commit 3a309e5

27 files changed

Lines changed: 1294 additions & 322 deletions

readme/FooterTemplate.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ DI.Setup("Composition")
729729
| [DisableAutoBindingLifetimeRegularExpression](#disableautobindinglifetimeregularexpression-hint) | Regular expression | | .+ |
730730
| [DisableAutoBindingLifetimeWildcard](#disableautobindinglifetimewildcard-hint) | Wildcard | | * |
731731
| [LightweightAnonymousRoot](#lightweightanonymousroot-hint) | _On_ or _Off_ | | _On_ |
732+
| [ScopeFactoryName](#scopefactoryname-hint) | Method name | | |
732733

733734
The list of hints will be gradually expanded to meet the needs and desires for fine-tuning code generation. Please feel free to add your ideas.
734735

@@ -1396,6 +1397,17 @@ DI.Setup("Composition")
13961397
.Bind<IService>().To<Service>();
13971398
```
13981399

1400+
### ScopeFactoryName Hint
1401+
1402+
Sets the scope factory name to be used for creating scopes.
1403+
1404+
```c#
1405+
// ScopeFactoryName = CreateScope
1406+
DI.Setup("Composition")
1407+
.Hint(Hint.ScopeFactoryName, "CreateScope")
1408+
.Bind<IDependency>().As(Lifetime.Scoped).To<Dependency>();
1409+
```
1410+
13991411
</details>
14001412

14011413
<details>

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+

22
// <auto-generated/>
33
#if !PUREDI_API_SUPPRESSION || PUREDI_API_V2
44
#pragma warning disable
@@ -1189,6 +1189,18 @@ internal enum Hint
11891189
/// </example>
11901190
/// </summary>
11911191
LightweightAnonymousRoot,
1192+
1193+
/// <summary>
1194+
/// Sets the scope factory name to be used for creating scopes.
1195+
/// <example>
1196+
/// <code>
1197+
/// DI.Setup("Composition")
1198+
/// .Hint(Hint.ScopeFactoryName, "CreateScope")
1199+
/// .Bind&lt;IDependency&gt;().As(Lifetime.Scoped).To&lt;Dependency&gt;();
1200+
/// </code>
1201+
/// </example>
1202+
/// </summary>
1203+
ScopeFactoryName,
11921204
}
11931205

11941206
/// <summary>

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

Lines changed: 12 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+
/// Sets the scope factory name to be used for creating scopes.
1200+
/// <example>
1201+
/// <code>
1202+
/// DI.Setup("Composition")
1203+
/// .Hint(Hint.ScopeFactoryName, "CreateScope")
1204+
/// .Bind&lt;IDependency&gt;().As(Lifetime.Scoped).To&lt;Dependency&gt;();
1205+
/// </code>
1206+
/// </example>
1207+
/// </summary>
1208+
ScopeFactoryName,
11971209
}
11981210

11991211
/// <summary>

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
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// ReSharper disable InvertIf
2+
namespace Pure.DI.Core.Code;
3+
4+
sealed class DisposalStatementsBuilder : IDisposalStatementsBuilder
5+
{
6+
public void AddDisposeLoop(
7+
Lines code,
8+
bool hasDisposable,
9+
bool hasAsyncDisposable,
10+
bool isAsync,
11+
string onDisposeExceptionCall,
12+
string onDisposeAsyncExceptionCall)
13+
{
14+
code.AppendLine("while (disposeIndex-- > 0)");
15+
using (code.CreateBlock())
16+
{
17+
code.AppendLine("switch (disposables[disposeIndex])");
18+
using (code.CreateBlock())
19+
{
20+
if (isAsync)
21+
{
22+
if (hasAsyncDisposable)
23+
{
24+
AddDisposeAsyncPart(code, true, onDisposeAsyncExceptionCall);
25+
}
26+
27+
if (hasDisposable)
28+
{
29+
if (hasAsyncDisposable)
30+
{
31+
code.AppendLine();
32+
}
33+
34+
AddDisposePart(code, onDisposeExceptionCall);
35+
}
36+
}
37+
else
38+
{
39+
if (hasDisposable)
40+
{
41+
AddDisposePart(code, onDisposeExceptionCall);
42+
}
43+
44+
if (hasAsyncDisposable)
45+
{
46+
if (hasDisposable)
47+
{
48+
code.AppendLine();
49+
}
50+
51+
AddDisposeAsyncPart(code, false, onDisposeAsyncExceptionCall);
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
private static void AddDisposeAsyncPart(Lines code, bool makeAsyncCall, string onDisposeAsyncExceptionCall)
59+
{
60+
code.AppendLine($"case {Names.IAsyncDisposableTypeName} asyncDisposableInstance:");
61+
using (code.Indent())
62+
{
63+
code.AppendLine("try");
64+
using (code.CreateBlock())
65+
{
66+
if (makeAsyncCall)
67+
{
68+
code.AppendLine("await asyncDisposableInstance.DisposeAsync();");
69+
}
70+
else
71+
{
72+
code.AppendLine("var valueTask = asyncDisposableInstance.DisposeAsync();");
73+
code.AppendLine("if (!valueTask.IsCompleted)");
74+
using (code.CreateBlock())
75+
{
76+
code.AppendLine("valueTask.AsTask().Wait();");
77+
}
78+
}
79+
}
80+
81+
code.AppendLine($"catch ({Names.ExceptionTypeName} exception)");
82+
using (code.CreateBlock())
83+
{
84+
code.AppendLine($"{onDisposeAsyncExceptionCall};");
85+
}
86+
87+
code.AppendLine("break;");
88+
}
89+
}
90+
91+
private static void AddDisposePart(Lines code, string onDisposeExceptionCall)
92+
{
93+
code.AppendLine($"case {Names.IDisposableTypeName} disposableInstance:");
94+
using (code.Indent())
95+
{
96+
code.AppendLine("try");
97+
using (code.CreateBlock())
98+
{
99+
code.AppendLine("disposableInstance.Dispose();");
100+
}
101+
102+
code.AppendLine($"catch ({Names.ExceptionTypeName} exception)");
103+
using (code.CreateBlock())
104+
{
105+
code.AppendLine($"{onDisposeExceptionCall};");
106+
}
107+
108+
code.AppendLine("break;");
109+
}
110+
}
111+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Pure.DI.Core.Code;
2+
3+
interface IDisposalStatementsBuilder
4+
{
5+
void AddDisposeLoop(
6+
Lines code,
7+
bool hasDisposable,
8+
bool hasAsyncDisposable,
9+
bool isAsync,
10+
string onDisposeExceptionCall,
11+
string onDisposeAsyncExceptionCall);
12+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ enum ClassPart
88
ParameterizedConstructor,
99
DefaultConstructor,
1010
ScopeConstructor,
11+
ScopeClass,
1112
RootMethods,
1213
ApiMembers,
1314
DisposeMethod,

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 hasScopeFactory = scopeStrategy == ScopeStrategy.ScopeFactory;
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 = hasScopeFactory
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 (hasScopeFactory && hasScopedLifetimes)
63+
{
64+
code.AppendLine($"{Names.RootScopeFieldName} = new {Names.ScopeClassName}(this, isRootScope: true);");
5365
}
5466
}
5567

0 commit comments

Comments
 (0)