Skip to content

Commit 5759e62

Browse files
committed
change ServiceDescriptor to use lifetime method and generics when available
1 parent 08b5b10 commit 5759e62

File tree

27 files changed

+181
-198
lines changed

27 files changed

+181
-198
lines changed

src/Injectio.Attributes/RegisterScopedAttribute.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class RegisterScopedAttribute : RegisterAttribute
2929
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
3030
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
3131
public class RegisterScopedAttribute<TService> : RegisterScopedAttribute
32+
where TService : class
3233
{
3334
/// <summary>
3435
/// Initializes a new instance of the <see cref="RegisterScopedAttribute"/> class.
@@ -53,6 +54,8 @@ public RegisterScopedAttribute()
5354
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
5455
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
5556
public class RegisterScopedAttribute<TService, TImplementation> : RegisterScopedAttribute<TService>
57+
where TService : class
58+
where TImplementation : class, TService
5659
{
5760
/// <summary>
5861
/// Initializes a new instance of the <see cref="RegisterScopedAttribute"/> class.

src/Injectio.Attributes/RegisterSingletonAttribute.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class RegisterSingletonAttribute : RegisterAttribute
2929
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
3030
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
3131
public class RegisterSingletonAttribute<TService> : RegisterSingletonAttribute
32+
where TService : class
3233
{
3334
/// <summary>
3435
/// Initializes a new instance of the <see cref="RegisterSingletonAttribute"/> class.
@@ -53,6 +54,8 @@ public RegisterSingletonAttribute()
5354
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
5455
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
5556
public class RegisterSingletonAttribute<TService, TImplementation> : RegisterSingletonAttribute<TService>
57+
where TService : class
58+
where TImplementation : class, TService
5659
{
5760
/// <summary>
5861
/// Initializes a new instance of the <see cref="RegisterSingletonAttribute"/> class.

src/Injectio.Attributes/RegisterTransientAttribute.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class RegisterTransientAttribute : RegisterAttribute
2929
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
3030
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
3131
public class RegisterTransientAttribute<TService> : RegisterTransientAttribute
32+
where TService : class
3233
{
3334
/// <summary>
3435
/// Initializes a new instance of the <see cref="RegisterTransientAttribute"/> class.
@@ -53,6 +54,8 @@ public RegisterTransientAttribute()
5354
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
5455
[System.Diagnostics.Conditional("REGISTER_SERVICE_USAGES")]
5556
public class RegisterTransientAttribute<TService, TImplementation> : RegisterTransientAttribute<TService>
57+
where TService : class
58+
where TImplementation : class, TService
5659
{
5760
/// <summary>
5861
/// Initializes a new instance of the <see cref="RegisterTransientAttribute"/> class.

src/Injectio.Generators/Injectio.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@
33
<CompilerVisibleProperty Include="InjectioName" />
44
<CompilerVisibleProperty Include="InjectioInternal" />
55
</ItemGroup>
6+
7+
<ItemGroup Condition="$(Language) == 'C#' and ($(ImplicitUsings) == 'enable' or $(ImplicitUsings) == 'true')">
8+
<Using Include="Injectio.Attributes" />
9+
</ItemGroup>
10+
611
</Project>

src/Injectio.Generators/KnownTypes.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ public static class KnownTypes
2626

2727
public const string ServiceLifetimeSingletonShortName = "Singleton";
2828
public const string ServiceLifetimeSingletonTypeName = $"ServiceLifetime.{ServiceLifetimeSingletonShortName}";
29+
public const string ServiceLifetimeSingletonFullName = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton";
2930

3031
public const string ServiceLifetimeScopedShortName = "Scoped";
3132
public const string ServiceLifetimeScopedTypeName = $"ServiceLifetime.{ServiceLifetimeScopedShortName}";
33+
public const string ServiceLifetimeScopedFullName = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped";
3234

3335
public const string ServiceLifetimeTransientShortName = "Transient";
3436
public const string ServiceLifetimeTransientTypeName = $"ServiceLifetime.{ServiceLifetimeTransientShortName}";
37+
public const string ServiceLifetimeTransientFullName = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient";
3538

3639

3740
public static readonly int DuplicateStrategySkipValue = 0;
@@ -59,5 +62,4 @@ public static class KnownTypes
5962
public const string RegistrationStrategySelfWithInterfacesShortName = "SelfWithInterfaces";
6063
public const string RegistrationStrategySelfWithInterfacesTypeName = $"RegistrationStrategy.{RegistrationStrategySelfWithInterfacesShortName}";
6164

62-
6365
}

src/Injectio.Generators/ServiceRegistration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public record ServiceRegistration(
1010
string? Factory,
1111
string Duplicate,
1212
string Registration,
13-
EquatableArray<string> Tags
13+
EquatableArray<string> Tags,
14+
bool IsOpenGeneric = false
1415
);

src/Injectio.Generators/ServiceRegistrationGenerator.cs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
256256
string? registrationStrategy = null;
257257
var tags = new HashSet<string>();
258258
string? serviceKey = null;
259+
bool isOpenGeneric = false;
259260

260261
var attributeClass = attribute.AttributeClass;
261262
if (attributeClass is { IsGenericType: true } && attributeClass.TypeArguments.Length == attributeClass.TypeParameters.Length)
@@ -290,13 +291,20 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
290291
switch (name)
291292
{
292293
case "ServiceType":
293-
serviceTypes.Add(value.ToString());
294+
var serviceTypeSymbol = value as INamedTypeSymbol;
295+
isOpenGeneric = isOpenGeneric || IsOpenGeneric(serviceTypeSymbol);
296+
297+
var serviceType = serviceTypeSymbol?.ToDisplayString(_fullyQualifiedNullableFormat) ?? value.ToString();
298+
serviceTypes.Add(serviceType);
294299
break;
295300
case "ServiceKey":
296301
serviceKey = parameter.Value.ToCSharpString();
297302
break;
298303
case "ImplementationType":
299-
implementationType = value.ToString();
304+
var implementationTypeSymbol = value as INamedTypeSymbol;
305+
isOpenGeneric = isOpenGeneric || IsOpenGeneric(implementationTypeSymbol);
306+
307+
implementationType = implementationTypeSymbol?.ToDisplayString(_fullyQualifiedNullableFormat) ?? value.ToString();
300308
break;
301309
case "Factory":
302310
implementationFactory = value.ToString();
@@ -334,7 +342,9 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
334342
// no implementation type set, use class attribute is on
335343
if (implementationType.IsNullOrWhiteSpace())
336344
{
337-
implementationType = ToNamedTypeWithoutPlaceholders(classSymbol).ToDisplayString(_fullyQualifiedNullableFormat);
345+
var unboundType = ToUnboundGenericType(classSymbol);
346+
isOpenGeneric = isOpenGeneric || IsOpenGeneric(unboundType);
347+
implementationType = unboundType.ToDisplayString(_fullyQualifiedNullableFormat);
338348
}
339349

340350
// add implemented interfaces
@@ -344,8 +354,13 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
344354
foreach (var implementedInterface in classSymbol.AllInterfaces)
345355
{
346356
// This interface is typically not injected into services and, more specifically, record types auto-implement it.
347-
if(implementedInterface.ConstructedFrom.ToString() == "System.IEquatable<T>") continue;
348-
var interfaceName = ToNamedTypeWithoutPlaceholders(implementedInterface).ToDisplayString(_fullyQualifiedNullableFormat);
357+
if(implementedInterface.ConstructedFrom.ToString() == "System.IEquatable<T>")
358+
continue;
359+
360+
var unboundInterface = ToUnboundGenericType(implementedInterface);
361+
isOpenGeneric = isOpenGeneric || IsOpenGeneric(unboundInterface);
362+
363+
var interfaceName = unboundInterface.ToDisplayString(_fullyQualifiedNullableFormat);
349364
serviceTypes.Add(interfaceName);
350365
}
351366
}
@@ -356,31 +371,27 @@ private static (EquatableArray<Diagnostic> diagnostics, bool hasServiceCollectio
356371
serviceTypes.Add(implementationType!);
357372

358373
return new ServiceRegistration(
359-
serviceLifetime,
360-
implementationType!,
361-
serviceTypes.ToArray(),
362-
serviceKey,
363-
implementationFactory,
364-
duplicateStrategy ?? KnownTypes.DuplicateStrategySkipShortName,
365-
registrationStrategy ?? KnownTypes.RegistrationStrategySelfWithInterfacesShortName,
366-
tags.ToArray());
374+
Lifetime: serviceLifetime,
375+
ImplementationType: implementationType!,
376+
ServiceTypes: serviceTypes.ToArray(),
377+
ServiceKey: serviceKey,
378+
Factory: implementationFactory,
379+
Duplicate: duplicateStrategy ?? KnownTypes.DuplicateStrategySkipShortName,
380+
Registration: registrationStrategy ?? KnownTypes.RegistrationStrategySelfWithInterfacesShortName,
381+
Tags: tags.ToArray(),
382+
IsOpenGeneric: isOpenGeneric);
367383
}
368384

369-
private static INamedTypeSymbol ToNamedTypeWithoutPlaceholders(INamedTypeSymbol typeSymbol)
385+
private static INamedTypeSymbol ToUnboundGenericType(INamedTypeSymbol typeSymbol)
370386
{
371-
if (!typeSymbol.IsGenericType
372-
|| typeSymbol.IsUnboundGenericType)
373-
{
387+
if (!typeSymbol.IsGenericType || typeSymbol.IsUnboundGenericType)
374388
return typeSymbol;
375-
}
376389

377390
foreach (var typeArgument in typeSymbol.TypeArguments)
378391
{
379392
// If TypeKind is TypeParameter, it's actually the name of a locally declared type-parameter -> placeholder
380393
if (typeArgument.TypeKind != TypeKind.TypeParameter)
381-
{
382394
return typeSymbol;
383-
}
384395
}
385396

386397
return typeSymbol.ConstructUnboundGenericType();
@@ -390,23 +401,23 @@ private static bool IsKnownAttribute(AttributeData attribute, out string service
390401
{
391402
if (IsSingletonAttribute(attribute))
392403
{
393-
serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton";
404+
serviceLifetime = KnownTypes.ServiceLifetimeSingletonFullName;
394405
return true;
395406
}
396407

397408
if (IsScopedAttribute(attribute))
398409
{
399-
serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped";
410+
serviceLifetime = KnownTypes.ServiceLifetimeScopedFullName;
400411
return true;
401412
}
402413

403414
if (IsTransientAttribute(attribute))
404415
{
405-
serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient";
416+
serviceLifetime = KnownTypes.ServiceLifetimeTransientFullName;
406417
return true;
407418
}
408419

409-
serviceLifetime = "Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient";
420+
serviceLifetime = KnownTypes.ServiceLifetimeTransientFullName;
410421
return false;
411422
}
412423

@@ -501,6 +512,17 @@ private static bool IsStringCollection(IParameterSymbol parameterSymbol)
501512
};
502513
}
503514

515+
private static bool IsOpenGeneric(INamedTypeSymbol? typeSymbol)
516+
{
517+
if (typeSymbol is null)
518+
return false;
519+
520+
if (!typeSymbol.IsGenericType)
521+
return false;
522+
523+
return typeSymbol.IsUnboundGenericType;
524+
}
525+
504526
private static string ResolveDuplicateStrategy(object? value)
505527
{
506528
return value switch

src/Injectio.Generators/ServiceRegistrationWriter.cs

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ private static void WriteRegistration(
161161
if (serviceType.IsNullOrWhiteSpace())
162162
continue;
163163

164-
WriteServiceType(codeBuilder, serviceRegistration, serviceMethod, serviceType);
164+
if (serviceRegistration.IsOpenGeneric)
165+
WriteServiceType(codeBuilder, serviceRegistration, serviceMethod, serviceType);
166+
else
167+
WriteServiceGeneric(codeBuilder, serviceRegistration, serviceMethod, serviceType);
165168
}
166169

167170
if (serviceRegistration.Tags.Count > 0)
@@ -179,14 +182,16 @@ private static void WriteServiceType(
179182
string serviceMethod,
180183
string serviceType)
181184
{
185+
var describeMethod = GetServiceDescriptorMethod(serviceRegistration);
186+
182187
codeBuilder
183188
.Append("global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.")
184189
.Append(serviceMethod)
185190
.AppendLine("(")
186191
.IncrementIndent()
187192
.AppendLine("serviceCollection,")
188193
.Append("global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.")
189-
.Append(serviceRegistration.ServiceKey.HasValue() ? "DescribeKeyed" : "Describe")
194+
.Append(describeMethod)
190195
.AppendLine("(")
191196
.IncrementIndent()
192197
.Append("typeof(")
@@ -228,9 +233,6 @@ private static void WriteServiceType(
228233
}
229234

230235
codeBuilder
231-
.AppendLine(",")
232-
.Append("global::")
233-
.Append(serviceRegistration.Lifetime)
234236
.AppendLine()
235237
.DecrementIndent()
236238
.AppendLine(")")
@@ -239,6 +241,64 @@ private static void WriteServiceType(
239241
.AppendLine();
240242
}
241243

244+
private static void WriteServiceGeneric(
245+
IndentedStringBuilder codeBuilder,
246+
ServiceRegistration serviceRegistration,
247+
string serviceMethod,
248+
string serviceType)
249+
{
250+
var describeMethod = GetServiceDescriptorMethod(serviceRegistration);
251+
252+
var implementationType = serviceRegistration.ImplementationType.HasValue()
253+
? serviceRegistration.ImplementationType
254+
: serviceType;
255+
256+
codeBuilder
257+
.Append("global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.")
258+
.Append(serviceMethod)
259+
.AppendLine("(")
260+
.IncrementIndent()
261+
.AppendLine("serviceCollection,")
262+
.Append("global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor.")
263+
.Append(describeMethod)
264+
.Append("<")
265+
.AppendIf("global::", !serviceType.StartsWith("global::"))
266+
.Append(serviceType);
267+
268+
// don't include the implementation type if there is factory
269+
if (serviceRegistration.Factory.IsNullOrEmpty())
270+
{
271+
codeBuilder
272+
.Append(", ")
273+
.AppendIf("global::", !implementationType.StartsWith("global::"))
274+
.Append(implementationType);
275+
}
276+
277+
codeBuilder.Append(">(");
278+
279+
// write service key argument
280+
if (serviceRegistration.ServiceKey.HasValue())
281+
codeBuilder.Append(serviceRegistration.ServiceKey);
282+
283+
// write factory method
284+
if (serviceRegistration.Factory.HasValue())
285+
{
286+
bool hasNamespace = serviceRegistration.Factory?.Contains(".") == true;
287+
288+
codeBuilder
289+
.AppendIf(", ", serviceRegistration.ServiceKey.HasValue()) // second argument if there is a service key
290+
.AppendIf(serviceRegistration.ImplementationType, !hasNamespace)
291+
.AppendIf(".", !hasNamespace)
292+
.Append(serviceRegistration.Factory);
293+
}
294+
295+
codeBuilder
296+
.AppendLine(")")
297+
.DecrementIndent()
298+
.AppendLine(");")
299+
.AppendLine();
300+
}
301+
242302
public static string GetServiceCollectionMethod(string duplicateStrategy)
243303
{
244304
return duplicateStrategy switch
@@ -253,4 +313,18 @@ public static string GetServiceCollectionMethod(string duplicateStrategy)
253313
};
254314
}
255315

316+
public static string GetServiceDescriptorMethod(ServiceRegistration serviceRegistration)
317+
{
318+
var describeMethod = serviceRegistration.Lifetime switch
319+
{
320+
KnownTypes.ServiceLifetimeSingletonFullName => "Singleton",
321+
KnownTypes.ServiceLifetimeScopedFullName => "Scoped",
322+
KnownTypes.ServiceLifetimeTransientFullName => "Transient",
323+
_ => "Transient"
324+
};
325+
326+
return serviceRegistration.ServiceKey.HasValue()
327+
? $"Keyed{describeMethod}"
328+
: describeMethod;
329+
}
256330
}

tests/Injectio.Tests.Console/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class LocalService : ILocalService { }
3535

3636
public interface ILocalAttributeService { }
3737

38-
[RegisterSingleton<ILocalAttributeService>]
38+
[RegisterSingleton<ILocalService>]
3939
public class LocalAttributeService : ILocalService, IService1 { }
4040

4141

@@ -69,7 +69,6 @@ public static void RegisterServices(IServiceCollection services)
6969
{
7070

7171
}
72-
7372
}
7473

7574
public class ServiceRegistrationInstance

0 commit comments

Comments
 (0)