Skip to content

Commit bf6efe5

Browse files
Introduce feature metadata system with MatrixFeatureAttribute and features.json integration
1 parent b4523f0 commit bf6efe5

32 files changed

Lines changed: 483 additions & 78 deletions

‎README.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,90 +115,120 @@ Performance and allocated memory are shown together. Lower values are better.
115115
<details>
116116
<summary><strong>01 · Singleton</strong></summary>
117117

118+
Registers three singleton services and resolves each of them repeatedly. Every resolve of the same service must return the same instance.
119+
118120
![Dependency Injection Singleton benchmark](reports/DependencyInjection/charts/01-singleton.png)
119121

120122
</details>
121123
<details>
122124
<summary><strong>02 · Transient</strong></summary>
123125

126+
Registers three transient services and resolves each of them repeatedly. Every resolve must create a new instance, never reusing an earlier one.
127+
124128
![Dependency Injection Transient benchmark](reports/DependencyInjection/charts/02-transient.png)
125129

126130
</details>
127131
<details>
128132
<summary><strong>03 · PerResolve</strong></summary>
129133

134+
Resolves an object graph that asks for the same dependency twice. Both requests inside one resolution share an instance, while the next resolution gets a new one.
135+
130136
![Dependency Injection PerResolve benchmark](reports/DependencyInjection/charts/03-perresolve.png)
131137

132138
</details>
133139
<details>
134140
<summary><strong>04 · Scoped</strong></summary>
135141

142+
Resolves scoped services inside explicit scopes. One instance per scope, different instances across scopes, and scope-owned disposables are disposed when the scope ends.
143+
136144
![Dependency Injection Scoped benchmark](reports/DependencyInjection/charts/04-scoped.png)
137145

138146
</details>
139147
<details>
140148
<summary><strong>05 · Combined</strong></summary>
141149

150+
Resolves three roots that mix singleton and transient dependencies. The singleton is shared across every root while each transient dependency is distinct.
151+
142152
![Dependency Injection Combined benchmark](reports/DependencyInjection/charts/05-combined.png)
143153

144154
</details>
145155
<details>
146156
<summary><strong>06 · Complex</strong></summary>
147157

158+
Registers and resolves three multi-level object graphs, checking that every nested dependency has the expected implementation type and lifetime.
159+
148160
![Dependency Injection Complex benchmark](reports/DependencyInjection/charts/06-complex.png)
149161

150162
</details>
151163
<details>
152164
<summary><strong>07 · Property</strong></summary>
153165

166+
Resolves three roots that carry writable service properties. The container, or its intended property-injection extension, must assign them during activation.
167+
154168
![Dependency Injection Property benchmark](reports/DependencyInjection/charts/07-property.png)
155169

156170
</details>
157171
<details>
158172
<summary><strong>08 · Generics</strong></summary>
159173

174+
Registers one open generic service mapping and resolves roots closed over int, float and object. Registering every closed type separately does not count.
175+
160176
![Dependency Injection Generics benchmark](reports/DependencyInjection/charts/08-generics.png)
161177

162178
</details>
163179
<details>
164180
<summary><strong>09 · IEnumerable</strong></summary>
165181

182+
Injects a sequence of five plugin implementations and requires it to be genuinely lazy: nothing is created until enumeration, and every enumeration yields new transients.
183+
166184
![Dependency Injection IEnumerable benchmark](reports/DependencyInjection/charts/09-ienumerable.png)
167185

168186
</details>
169187
<details>
170188
<summary><strong>10 · Array</strong></summary>
171189

190+
Resolves three roots that materialise their injected sequence of five plugins into an array while the root is being activated.
191+
172192
![Dependency Injection Array benchmark](reports/DependencyInjection/charts/10-array.png)
173193

174194
</details>
175195
<details>
176196
<summary><strong>11 · Conditional</strong></summary>
177197

198+
Gives each of three consumers a different implementation of one contract, chosen through the metadata, key, predicate or consumer-context mechanism of the library.
199+
178200
![Dependency Injection Conditional benchmark](reports/DependencyInjection/charts/11-conditional.png)
179201

180202
</details>
181203
<details>
182204
<summary><strong>12 · Child Container</strong></summary>
183205

206+
Creates a real nested child container that inherits the registrations of its parent and can add or override them without changing the parent.
207+
184208
![Dependency Injection Child Container benchmark](reports/DependencyInjection/charts/12-child-container.png)
185209

186210
</details>
187211
<details>
188212
<summary><strong>13 · Interception With Proxy</strong></summary>
189213

214+
Resolves a service through the interception or activation extension point of the library. The result must be a proxy whose interceptor proceeds to the real target.
215+
190216
![Dependency Injection Interception With Proxy benchmark](reports/DependencyInjection/charts/13-interception-with-proxy.png)
191217

192218
</details>
193219
<details>
194220
<summary><strong>14 · Prepare And Register</strong></summary>
195221

222+
Measures creating the container and registering the whole prescribed graph, without resolving anything from it.
223+
196224
![Dependency Injection Prepare And Register benchmark](reports/DependencyInjection/charts/14-prepare-and-register.png)
197225

198226
</details>
199227
<details>
200228
<summary><strong>15 · Prepare And Register And Simple Resolve</strong></summary>
201229

230+
Measures the same setup as Prepare And Register, followed by a single resolve of one singleton root.
231+
202232
![Dependency Injection Prepare And Register And Simple Resolve benchmark](reports/DependencyInjection/charts/15-prepare-and-register-and-simple-resolve.png)
203233

204234
</details>

‎build/Targets/MetadataTarget.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public int Run(IReadOnlyList<DiscoveredMatrixModule> modules)
4242
JsonSerializer.Serialize(module.Metadata.LibraryMetadata, JsonOptions)
4343
+ Environment.NewLine);
4444
Console.WriteLine($"Library metadata: {path}");
45+
46+
var featurePath = Path.Combine(directory, "features.json");
47+
File.WriteAllText(
48+
featurePath,
49+
JsonSerializer.Serialize(module.Metadata.FeatureMetadata, JsonOptions)
50+
+ Environment.NewLine);
51+
Console.WriteLine($"Feature metadata: {featurePath}");
4552
}
4653

4754
return 0;

‎build/Targets/ReadmeTarget.cs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Matrix;
1+
using Matrix;
22
using System.Text.Json;
33

44
namespace Build.Targets;
@@ -116,6 +116,10 @@ await templateEngine.RenderAsync(
116116
.Select(feature => new ReadmeFeature(
117117
feature.Order,
118118
feature.Name,
119+
module.Metadata.FeatureMetadata.Features
120+
.FirstOrDefault(item =>
121+
item.Id.Equals(feature.Id, StringComparison.OrdinalIgnoreCase))
122+
?.Description,
119123
RelativePath(Path.Combine(
120124
reportRoot,
121125
MatrixChartPaths.DirectoryName,
@@ -160,4 +164,5 @@ public sealed record ReadmeChart(
160164
public sealed record ReadmeFeature(
161165
int Order,
162166
string Name,
167+
string? Description,
163168
string ChartPath);

‎build/Templates/Readme.cshtml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model Build.Targets.ReadmeModel
1+
@model Build.Targets.ReadmeModel
22
# .NET Matrix
33

44
> [**Open the interactive .NET Matrix →**](@Model.ApplicationUrl)
@@ -43,6 +43,12 @@ else
4343
<details>
4444
<summary><strong>@feature.Order.ToString("00") · @feature.Name</strong></summary>
4545

46+
@if (feature.Description is not null)
47+
{
48+
<text>@feature.Description
49+
50+
</text>
51+
}
4652
![@category.Name @feature.Name benchmark](@feature.ChartPath)
4753

4854
</details>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"schemaVersion": 1,
3+
"features": [
4+
{
5+
"id": "Singleton",
6+
"order": 1,
7+
"name": "Singleton",
8+
"description": "Registers three singleton services and resolves each of them repeatedly. Every resolve of the same service must return the same instance."
9+
},
10+
{
11+
"id": "Transient",
12+
"order": 2,
13+
"name": "Transient",
14+
"description": "Registers three transient services and resolves each of them repeatedly. Every resolve must create a new instance, never reusing an earlier one."
15+
},
16+
{
17+
"id": "PerResolve",
18+
"order": 3,
19+
"name": "PerResolve",
20+
"description": "Resolves an object graph that asks for the same dependency twice. Both requests inside one resolution share an instance, while the next resolution gets a new one."
21+
},
22+
{
23+
"id": "Scoped",
24+
"order": 4,
25+
"name": "Scoped",
26+
"description": "Resolves scoped services inside explicit scopes. One instance per scope, different instances across scopes, and scope-owned disposables are disposed when the scope ends."
27+
},
28+
{
29+
"id": "Combined",
30+
"order": 5,
31+
"name": "Combined",
32+
"description": "Resolves three roots that mix singleton and transient dependencies. The singleton is shared across every root while each transient dependency is distinct."
33+
},
34+
{
35+
"id": "Complex",
36+
"order": 6,
37+
"name": "Complex",
38+
"description": "Registers and resolves three multi-level object graphs, checking that every nested dependency has the expected implementation type and lifetime."
39+
},
40+
{
41+
"id": "Property",
42+
"order": 7,
43+
"name": "Property",
44+
"description": "Resolves three roots that carry writable service properties. The container, or its intended property-injection extension, must assign them during activation."
45+
},
46+
{
47+
"id": "Generics",
48+
"order": 8,
49+
"name": "Generics",
50+
"description": "Registers one open generic service mapping and resolves roots closed over int, float and object. Registering every closed type separately does not count."
51+
},
52+
{
53+
"id": "Enumerable",
54+
"order": 9,
55+
"name": "IEnumerable",
56+
"description": "Injects a sequence of five plugin implementations and requires it to be genuinely lazy: nothing is created until enumeration, and every enumeration yields new transients."
57+
},
58+
{
59+
"id": "Array",
60+
"order": 10,
61+
"name": "Array",
62+
"description": "Resolves three roots that materialise their injected sequence of five plugins into an array while the root is being activated."
63+
},
64+
{
65+
"id": "Conditional",
66+
"order": 11,
67+
"name": "Conditional",
68+
"description": "Gives each of three consumers a different implementation of one contract, chosen through the metadata, key, predicate or consumer-context mechanism of the library."
69+
},
70+
{
71+
"id": "ChildContainer",
72+
"order": 12,
73+
"name": "Child Container",
74+
"description": "Creates a real nested child container that inherits the registrations of its parent and can add or override them without changing the parent."
75+
},
76+
{
77+
"id": "InterceptionWithProxy",
78+
"order": 13,
79+
"name": "Interception With Proxy",
80+
"description": "Resolves a service through the interception or activation extension point of the library. The result must be a proxy whose interceptor proceeds to the real target."
81+
},
82+
{
83+
"id": "PrepareAndRegister",
84+
"order": 14,
85+
"name": "Prepare And Register",
86+
"description": "Measures creating the container and registering the whole prescribed graph, without resolving anything from it."
87+
},
88+
{
89+
"id": "PrepareAndRegisterAndSimpleResolve",
90+
"order": 15,
91+
"name": "Prepare And Register And Simple Resolve",
92+
"description": "Measures the same setup as Prepare And Register, followed by a single resolve of one singleton root."
93+
}
94+
]
95+
}

‎src/Matrix.DependencyInjection/Benchmarking/BenchmarkRun.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BenchmarkDotNet.Configs;
1+
using BenchmarkDotNet.Configs;
22
using BenchmarkDotNet.Exporters.Json;
33
using BenchmarkDotNet.Jobs;
44
using BenchmarkDotNet.Running;
@@ -86,13 +86,13 @@ public int Run(IReadOnlyList<MatrixLibrary> libraries, RunnerOptions options)
8686
{
8787
var method = report.BenchmarkCase.Descriptor.WorkloadMethod;
8888
var library = method.GetCustomAttribute<LibraryBenchmarkAttribute>()!;
89-
var feature = method.DeclaringType!.GetCustomAttribute<FeatureBenchmarkAttribute>()!;
89+
var feature = method.DeclaringType!.GetCustomAttribute<MatrixFeatureAttribute>()!;
9090
var allocatedBytes = report.Metrics.TryGetValue("Allocated Memory", out var allocated)
9191
? allocated.Value
9292
: (double?)null;
9393
return new CapturedBenchmarkResult(
9494
feature.Order,
95-
feature.Id.ToString(),
95+
feature.Id,
9696
feature.Name,
9797
new BenchmarkResult(
9898
library.LibraryId,
@@ -242,7 +242,7 @@ private static CapturedBenchmarkResult[] CaptureReportedResults(
242242
.GetTypes()
243243
.SelectMany(type =>
244244
{
245-
var feature = type.GetCustomAttribute<FeatureBenchmarkAttribute>();
245+
var feature = type.GetCustomAttribute<MatrixFeatureAttribute>();
246246
if (feature is null)
247247
{
248248
return [];
@@ -267,7 +267,7 @@ item.Library is not null
267267
})
268268
.Select(item => new CapturedBenchmarkResult(
269269
item.Feature.Order,
270-
item.Feature.Id.ToString(),
270+
item.Feature.Id,
271271
item.Feature.Name,
272272
new BenchmarkResult(
273273
item.Library!.LibraryId,

‎src/Matrix.DependencyInjection/Benchmarks/Common/01_Singleton.cs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
// ReSharper disable CheckNamespace
1+
// ReSharper disable CheckNamespace
22
// ReSharper disable InconsistentNaming
33

44
namespace Matrix.DependencyInjection.Benchmarks;
55

6-
[FeatureBenchmark(FeatureId.Singleton, 1, "Singleton")]
6+
[MatrixFeature(
7+
nameof(FeatureId.Singleton),
8+
1,
9+
"Singleton",
10+
"Registers three singleton services and resolves each of them repeatedly. Every resolve of the same service must return the same instance.")]
711
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
812
[MemoryDiagnoser]
913
public partial class Singleton

‎src/Matrix.DependencyInjection/Benchmarks/Common/02_Transient.cs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
// ReSharper disable CheckNamespace
1+
// ReSharper disable CheckNamespace
22
// ReSharper disable InconsistentNaming
33

44
namespace Matrix.DependencyInjection.Benchmarks;
55

6-
[FeatureBenchmark(FeatureId.Transient, 2, "Transient")]
6+
[MatrixFeature(
7+
nameof(FeatureId.Transient),
8+
2,
9+
"Transient",
10+
"Registers three transient services and resolves each of them repeatedly. Every resolve must create a new instance, never reusing an earlier one.")]
711
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
812
[MemoryDiagnoser]
913
public partial class Transient

‎src/Matrix.DependencyInjection/Benchmarks/Common/03_PerResolve.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
namespace Matrix.DependencyInjection.Benchmarks;
55

6-
[FeatureBenchmark(FeatureId.PerResolve, 3, "PerResolve")]
6+
[MatrixFeature(
7+
nameof(FeatureId.PerResolve),
8+
3,
9+
"PerResolve",
10+
"Resolves an object graph that asks for the same dependency twice. Both requests inside one resolution share an instance, while the next resolution gets a new one.")]
711
[FeatureUnavailable(
812
LibraryCatalog.Autofac,
913
FeatureStatus.Unsupported,

‎src/Matrix.DependencyInjection/Benchmarks/Common/04_Scoped.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
namespace Matrix.DependencyInjection.Benchmarks;
55

6-
[FeatureBenchmark(FeatureId.Scoped, 4, "Scoped")]
6+
[MatrixFeature(
7+
nameof(FeatureId.Scoped),
8+
4,
9+
"Scoped",
10+
"Resolves scoped services inside explicit scopes. One instance per scope, different instances across scopes, and scope-owned disposables are disposed when the scope ends.")]
711
[FeatureUnavailable(
812
LibraryCatalog.ZenIoc,
913
FeatureStatus.Unsupported,

0 commit comments

Comments
 (0)