Skip to content

Commit 4dc3486

Browse files
authored
Merge pull request #31 from kristoffer-tungland/codex/implement-dependency-injection-feature
Refine factory generation to use cached ObjectFactory
2 parents 1034f1f + 4b23bf3 commit 4dc3486

18 files changed

Lines changed: 751 additions & 40 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
13+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
14+
<!-- Reference the source generator project as an analyzer so generated code is available at compile time -->
15+
<ProjectReference Include="..\DependencyInjection.SourceGenerator.Microsoft\DependencyInjection.SourceGenerator.Microsoft.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using BenchmarkDotNet.Attributes;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace DependencyInjection.SourceGenerator.Benchmarks;
5+
6+
[MemoryDiagnoser]
7+
public class FactoryBenchmarks
8+
{
9+
private ITestServiceFactory _factory = null!;
10+
11+
[GlobalSetup]
12+
public void Setup()
13+
{
14+
var services = new ServiceCollection();
15+
// The generator will emit an extension named based on the assembly name
16+
// For this project the generator produces: AddDependencyInjectionSourceGeneratorBenchmarks()
17+
services.AddDependencyInjectionSourceGeneratorBenchmarks();
18+
var provider = services.BuildServiceProvider();
19+
_factory = provider.GetRequiredService<ITestServiceFactory>();
20+
}
21+
22+
[Benchmark]
23+
public TestService CreateWithFactory()
24+
{
25+
return _factory.Create(42);
26+
}
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using BenchmarkDotNet.Running;
2+
3+
namespace DependencyInjection.SourceGenerator.Benchmarks;
4+
5+
public class Program
6+
{
7+
public static void Main(string[] args)
8+
{
9+
var summary = BenchmarkRunner.Run<FactoryBenchmarks>();
10+
}
11+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace DependencyInjection.SourceGenerator.Benchmarks;
4+
5+
#pragma warning disable CS9113
6+
7+
[Register]
8+
public class TestService
9+
{
10+
public TestService(TestDependency _, [FactoryArgument] int value)
11+
{
12+
Value = value;
13+
}
14+
15+
public int Value { get; }
16+
}
17+
18+
[Register]
19+
public class TestDependency(IServiceProvider _) { };
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
// See https://aka.ms/new-console-template for more information
2-
Console.WriteLine("Hello, World!");
2+
using DependencyInjection.SourceGenerator.Microsoft.Demo;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
var services = new ServiceCollection();
6+
services.AddDependencyInjectionSourceGeneratorMicrosoftDemo();
7+
var provider = services.BuildServiceProvider();
8+
9+
var factory = provider.GetRequiredService<ITestServiceFactory>();
10+
var testService = factory.Create(42);
11+
Console.WriteLine(testService.Value);
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1-
namespace DependencyInjection.SourceGenerator.Microsoft.Demo;
1+
using Microsoft.Extensions.DependencyInjection;
22

3-
[global::Microsoft.Extensions.DependencyInjection.Register]
3+
namespace DependencyInjection.SourceGenerator.Microsoft.Demo;
4+
5+
#pragma warning disable CS9113
6+
7+
[Register]
48
public class TestService
59
{
10+
public TestService(TestDependency _, [FactoryArgument] int value)
11+
{
12+
Value = value;
13+
}
14+
15+
public int Value { get; }
16+
}
617

7-
}
18+
[Register]
19+
public class TestDependency(IServiceProvider _) { };

DependencyInjection.SourceGenerator.Microsoft.Tests/DependencyInjectionRegistrationGeneratorTests.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.CodeAnalysis;
2+
using System;
23
using System.Collections.Immutable;
34
using FluentAssertions;
45
using Microsoft.CodeAnalysis.CSharp;
@@ -55,13 +56,22 @@ private static async Task RunTestAsync(string code, [CallerMemberName] string me
5556

5657
foreach (var syntaxTree in outputCompilation.SyntaxTrees)
5758
{
58-
if (syntaxTree.FilePath.EndsWith("ServiceRegistrations.g.cs") == false)
59+
var fileName = Path.GetFileName(syntaxTree.FilePath);
60+
if (fileName is null)
61+
{
5962
continue;
63+
}
64+
65+
if (fileName.EndsWith("ServiceRegistrations.g.cs", StringComparison.Ordinal) == false &&
66+
fileName.EndsWith("Factory.g.cs", StringComparison.Ordinal) == false)
67+
{
68+
continue;
69+
}
6070

6171
var generatedSource = syntaxTree.ToString().Replace("\r\n", "\n");
6272
var settings = new VerifySettings();
6373
settings.UseDirectory("TestResults");
64-
settings.UseFileName(methodName + "_" + Path.GetFileNameWithoutExtension(syntaxTree.FilePath));
74+
settings.UseFileName(methodName + "_" + Path.GetFileNameWithoutExtension(fileName));
6575
await Verifier.Verify(generatedSource, settings);
6676
}
6777
}
@@ -121,6 +131,34 @@ namespace DependencyInjection.SourceGenerator.Microsoft.Demo;
121131
public class Service : IService {}
122132
public interface IService {}
123133
134+
""";
135+
136+
await RunTestAsync(code);
137+
}
138+
139+
[Fact]
140+
public async Task Register_WithFactoryArguments()
141+
{
142+
var code = """
143+
using global::Microsoft.Extensions.DependencyInjection;
144+
145+
namespace DependencyInjection.SourceGenerator.Microsoft.Demo;
146+
147+
[Register(ServiceType = typeof(IReportJob), Lifetime = ServiceLifetime.Scoped)]
148+
public sealed class ReportJob : IReportJob
149+
{
150+
public ReportJob(IDependency dependency, [FactoryArgument] System.Guid reportId)
151+
{
152+
}
153+
}
154+
155+
public interface IReportJob
156+
{
157+
}
158+
159+
public interface IDependency
160+
{
161+
}
124162
""";
125163

126164
await RunTestAsync(code);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// <auto-generated/>
2+
#pragma warning disable
3+
#nullable enable
4+
namespace DependencyInjection.SourceGenerator.Microsoft.Demo;
5+
public interface IReportJobFactory
6+
{
7+
global::DependencyInjection.SourceGenerator.Microsoft.Demo.IReportJob Create(global::System.Guid reportId);
8+
}
9+
10+
[global::System.CodeDom.Compiler.GeneratedCode("DependencyInjection.SourceGenerator.Microsoft", "3.1.0.0")]
11+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
12+
public sealed class ReportJobFactory : IReportJobFactory
13+
{
14+
private static readonly global::Microsoft.Extensions.DependencyInjection.ObjectFactory _objectFactory = global::Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactory(typeof(global::DependencyInjection.SourceGenerator.Microsoft.Demo.ReportJob), new global::System.Type[] { typeof(global::System.Guid) });
15+
private readonly global::System.IServiceProvider _serviceProvider;
16+
public ReportJobFactory(global::System.IServiceProvider serviceProvider)
17+
{
18+
_serviceProvider = serviceProvider;
19+
}
20+
21+
public global::DependencyInjection.SourceGenerator.Microsoft.Demo.IReportJob Create(global::System.Guid reportId)
22+
{
23+
return (global::DependencyInjection.SourceGenerator.Microsoft.Demo.IReportJob)_objectFactory(_serviceProvider, new object[] { reportId });
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// <auto-generated/>
2+
#pragma warning disable
3+
#nullable enable
4+
namespace Microsoft.Extensions.DependencyInjection;
5+
using global::Microsoft.Extensions.DependencyInjection;
6+
7+
public static partial class ServiceCollectionExtensions
8+
{
9+
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddTestProject(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)
10+
{
11+
services.AddScoped<global::DependencyInjection.SourceGenerator.Microsoft.Demo.IReportJobFactory, global::DependencyInjection.SourceGenerator.Microsoft.Demo.ReportJobFactory>();
12+
return services;
13+
}
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Microsoft.Extensions.DependencyInjection;
2+
3+
[AttributeUsage(AttributeTargets.Parameter)]
4+
internal sealed class FactoryArgumentAttribute : Attribute
5+
{
6+
}

0 commit comments

Comments
 (0)