-
Notifications
You must be signed in to change notification settings - Fork 0
04 04 Collection Declaration
The collection class is a partial class with the [TypeCollection] attribute. The source generator fills in the implementation.
From PaymentMethods.cs:1-34:
using FractalDataWorks.Collections;
using FractalDataWorks.Collections.Attributes;
namespace Reference.TypeCollections.Basic;
/// <summary>
/// TypeCollection for payment methods.
/// Source generator creates static properties, lookup methods, and FrozenDictionary storage.
/// </summary>
/// <remarks>
/// <para>
/// This is an <b>immutable</b> TypeCollection - all options are discovered at compile time
/// and stored in a FrozenDictionary for O(1) lookups.
/// </para>
/// <para>
/// Generated members include:
/// <list type="bullet">
/// <item>Static properties: PaymentMethods.Cash, PaymentMethods.CreditCard, etc.</item>
/// <item>ById(int id) - O(1) lookup by ID</item>
/// <item>ByName(string name) - O(1) lookup by name</item>
/// <item>All() - Returns all payment methods</item>
/// <item>Empty - Sentinel value for "no payment method"</item>
/// </list>
/// </para>
/// </remarks>
[TypeCollection(typeof(PaymentMethodBase), typeof(IPaymentMethod), typeof(PaymentMethods))]
public abstract partial class PaymentMethods : TypeCollectionBase<PaymentMethodBase, IPaymentMethod>
{
// Source generator populates this class with:
// - Static constructor initializing FrozenDictionaries
// - Static properties for each [TypeOption] payment method
// - ById(), ByName(), All() methods
// - Empty sentinel value
}From TypeCollectionAttribute.cs:12-28:
[TypeCollection(typeof(BaseType), typeof(InterfaceType), typeof(CollectionType))]| Parameter | Description |
|---|---|
BaseType |
The base class for options (e.g., PaymentMethodBase) |
InterfaceType |
The interface type (e.g., IPaymentMethod) |
CollectionType |
This collection class (e.g., PaymentMethods) |
The source generator creates the following members (see TypeCollectionGenerator.cs):
// Static accessors for each [TypeOption] discovered
public static CashPayment Cash { get; }
public static CreditCardPayment CreditCard { get; }
public static BankTransferPayment BankTransfer { get; }
// Lookup methods
public static IPaymentMethod ByName(string? name);
public static IPaymentMethod ById(int id);
public static IReadOnlyCollection<IPaymentMethod> All();
// Empty sentinel returned when lookups fail
public static IPaymentMethod Empty { get; }
// Registration for cross-assembly discovery
public static void RegisterMember(IPaymentMethod type);TypeOptions can be defined in separate NuGet packages. The deferred freeze pattern ensures they're registered before the collection is accessed.
Static constructors only run when the assembly is accessed. If you call PaymentMethods.All() before loading a custom payment method package, those options won't be included.
sequenceDiagram
participant App as Application
participant Col as PaymentMethods
participant Lib as CustomPayments Package
Note over App: Application starts
App->>Col: PaymentMethods.All()
Note over Col: Collection freezes!
Col-->>App: Returns built-in methods only
Note over Lib: Custom methods never registered
The Registration.SourceGenerators package generates module initializers in consuming assemblies:
// Generated in YOUR application
[ModuleInitializer]
internal static void RegisterTypeOptions()
{
PaymentMethods.RegisterMember(new BitcoinPayment());
}This runs before Main(), ensuring all TypeOptions are registered.
-
Before first access: Options collected via
RegisterMember() -
On first access: Collection freezes into
FrozenDictionary -
After freeze:
RegisterMember()throws exception
public static void RegisterMember(IPaymentMethod type)
{
if (_frozen)
throw new InvalidOperationException("Collection is frozen");
lock (_lock)
{
if (!_pendingRegistrations.Any(p => p.Id == type.Id))
_pendingRegistrations.Add(type);
}
}To enable transitive registration for consumers, embed the Registration generator:
<ItemGroup>
<!-- Embed for transitive flow -->
<None Include="$(OutputPath)FractalDataWorks.Registration.SourceGenerators.dll"
Pack="true"
PackagePath="analyzers/dotnet/cs" />
</ItemGroup>Consumers don't need to reference any generator directly - it flows through the dependency chain.
Basic/
|-- IPaymentMethod.cs
|-- PaymentMethodBase.cs
|-- PaymentMethods.cs
|-- Options/
|-- CashPayment.cs
|-- CreditCardPayment.cs
|-- BankTransferPayment.cs
- Generated Lookups - Using the generated code