| | | 1 | | using System; |
| | | 2 | | |
| | | 3 | | namespace NexusLabs.Needlr.Generators; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Marks an open generic <em>composition</em> class so that the source generator registers a closed |
| | | 7 | | /// instance of it for <strong>each</strong> discovered concrete implementation of a designated |
| | | 8 | | /// open generic interface, exposed as a designated non-generic (or less-generic) facade service type. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// <para> |
| | | 12 | | /// This is a source-generation only feature. It requires the <c>NexusLabs.Needlr.Generators</c> |
| | | 13 | | /// package and has no effect when using reflection-based registration. |
| | | 14 | | /// </para> |
| | | 15 | | /// <para> |
| | | 16 | | /// Use this attribute to keep a "compose-and-expose" pattern on the source-generation path instead of |
| | | 17 | | /// hand-maintaining one registration line per type argument (which silently drifts) or falling back to |
| | | 18 | | /// runtime reflection (which is AOT/trimming hostile). For every concrete closed implementation of the |
| | | 19 | | /// designated open generic interface that the generator discovers, it closes this composition class over |
| | | 20 | | /// the <em>same</em> type argument(s) and registers it as the service type named by <see cref="As"/>, |
| | | 21 | | /// resolving the composition's constructor dependencies from the service provider. |
| | | 22 | | /// </para> |
| | | 23 | | /// <para> |
| | | 24 | | /// The annotated class must: |
| | | 25 | | /// <list type="bullet"> |
| | | 26 | | /// <item><description>Be an open generic class whose type-parameter arity matches the open generic interface.</descript |
| | | 27 | | /// <item><description>Implement the service type specified by <see cref="As"/>.</description></item> |
| | | 28 | | /// <item><description>Have a single public constructor whose parameters are all resolvable from the service provider.</ |
| | | 29 | | /// </list> |
| | | 30 | | /// </para> |
| | | 31 | | /// <para> |
| | | 32 | | /// A discovered type argument that does not satisfy the composition class's generic constraints is |
| | | 33 | | /// skipped with a build diagnostic (<c>NDLRGEN038</c>) rather than producing a runtime failure. |
| | | 34 | | /// </para> |
| | | 35 | | /// </remarks> |
| | | 36 | | /// <example> |
| | | 37 | | /// <code> |
| | | 38 | | /// // Auto-discovered today: concrete, closed, unattributed. |
| | | 39 | | /// public interface IFooDefinition<TData> where TData : class |
| | | 40 | | /// { |
| | | 41 | | /// string Discriminator { get; } |
| | | 42 | | /// } |
| | | 43 | | /// |
| | | 44 | | /// public sealed class AlphaFoo : IFooDefinition<AlphaData> { /* ... */ } |
| | | 45 | | /// public sealed class BetaFoo : IFooDefinition<BetaData> { /* ... */ } |
| | | 46 | | /// |
| | | 47 | | /// // Non-generic facade consumed as IEnumerable<IFoo>. |
| | | 48 | | /// public interface IFoo { string Discriminator { get; } } |
| | | 49 | | /// |
| | | 50 | | /// // Reusable composition closed per discovered TData and exposed as IFoo. |
| | | 51 | | /// [RegisterClosedOverImplementationsOf(typeof(IFooDefinition<>), As = typeof(IFoo))] |
| | | 52 | | /// public sealed class FooCore<TData> : IFoo where TData : class |
| | | 53 | | /// { |
| | | 54 | | /// public FooCore(IFooDefinition<TData> definition, IFooStore<TData> store) { /* ... */ } |
| | | 55 | | /// public string Discriminator => /* delegates to definition */ ""; |
| | | 56 | | /// } |
| | | 57 | | /// |
| | | 58 | | /// // Generator emits, per discovered implementation: |
| | | 59 | | /// // services.AddSingleton<IFoo>(sp => new FooCore<AlphaData>( |
| | | 60 | | /// // sp.GetRequiredService<IFooDefinition<AlphaData>>(), |
| | | 61 | | /// // sp.GetRequiredService<IFooStore<AlphaData>>())); |
| | | 62 | | /// // services.AddSingleton<IFoo>(sp => new FooCore<BetaData>( ... )); |
| | | 63 | | /// </code> |
| | | 64 | | /// </example> |
| | | 65 | | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] |
| | | 66 | | public sealed class RegisterClosedOverImplementationsOfAttribute : Attribute |
| | | 67 | | { |
| | | 68 | | /// <summary> |
| | | 69 | | /// Initializes a new instance of the <see cref="RegisterClosedOverImplementationsOfAttribute"/> class. |
| | | 70 | | /// </summary> |
| | | 71 | | /// <param name="sourceOpenGenericInterface"> |
| | | 72 | | /// The open generic interface whose concrete closed implementations drive registration |
| | | 73 | | /// (e.g., <c>typeof(IFooDefinition<>)</c>). Must be an open generic interface. |
| | | 74 | | /// </param> |
| | 1230 | 75 | | public RegisterClosedOverImplementationsOfAttribute(Type sourceOpenGenericInterface) |
| | | 76 | | { |
| | 1230 | 77 | | SourceOpenGenericInterface = sourceOpenGenericInterface; |
| | 1230 | 78 | | } |
| | | 79 | | |
| | | 80 | | /// <summary> |
| | | 81 | | /// Gets the open generic interface whose concrete closed implementations drive registration. |
| | | 82 | | /// </summary> |
| | 0 | 83 | | public Type SourceOpenGenericInterface { get; } |
| | | 84 | | |
| | | 85 | | /// <summary> |
| | | 86 | | /// Gets or sets the service type that each closed composition is registered as |
| | | 87 | | /// (e.g., <c>typeof(IFoo)</c>). Must be a type implemented by the annotated composition class. |
| | | 88 | | /// </summary> |
| | 1230 | 89 | | public Type? As { get; set; } |
| | | 90 | | |
| | | 91 | | /// <summary> |
| | | 92 | | /// Gets or sets the lifetime each closed composition registration is given. |
| | | 93 | | /// Defaults to <see cref="InjectableLifetime.Singleton"/>. |
| | | 94 | | /// </summary> |
| | 820 | 95 | | public InjectableLifetime Lifetime { get; set; } = InjectableLifetime.Singleton; |
| | | 96 | | } |