Skip to content

Generator: Create Flyweight Pattern #38

@JerrettDavis

Description

@JerrettDavis

Generator: Create Flyweight Pattern

Summary

Add a source generator that produces boilerplate-free, GoF-consistent Flyweight pattern implementations with explicit key semantics, deterministic caching, and configurable eviction.

The generator lives in PatternKit.Generators and emits self-contained C# with no runtime PatternKit dependency.

Primary goals:

  • Generate flyweight caches with explicit keys and factory creation.
  • Make thread-safety, eviction, and comparer behavior explicit.
  • Provide deterministic policies and clear diagnostics.

Motivation / Problem

Flyweight is often re-implemented ad hoc:

  • inconsistent keying/comparers
  • unclear threading guarantees
  • unbounded caches that leak memory
  • hard-to-test eviction policies

A generator can emit a correct, consistent flyweight cache once.


Supported Targets (must-have)

The generator must support creating flyweights for:

  • partial class / partial struct
  • partial record class / partial record struct

Two usage models:

  1. Factory-on-type: annotate the flyweight value type; generator emits a cache type.
  2. Cache host: annotate a host type that declares key/value types explicitly.

Proposed User Experience

A) Annotate value type, generate cache

[Flyweight(typeof(string), CacheTypeName = "GlyphCache")]
public partial record struct Glyph(char Value, int Width);

public static partial class Glyph
{
    [FlyweightFactory]
    private static Glyph Create(string key) => /* ... */;
}

Generated (representative shape):

public sealed partial class GlyphCache
{
    public GlyphCache(IEqualityComparer<string>? comparer = null);

    public Glyph Get(string key);
    public bool TryGet(string key, out Glyph value);
    public void Clear();
}

B) Bounded cache

[Flyweight(typeof(string), Capacity = 10_000, Eviction = FlyweightEviction.Lru)]
public partial record struct Glyph(char Value, int Width);

Generated cache uses deterministic eviction.


Attributes / Surface Area

Namespace: PatternKit.Generators.Flyweight

Core

  • [Flyweight] on value type or cache host

    • Type KeyType
    • string? CacheTypeName (default: <ValueTypeName>FlyweightCache)
    • int Capacity (default: 0 = unbounded)
    • FlyweightEviction Eviction (default: None)
    • FlyweightThreadingPolicy Threading (default: Locking)
    • bool GenerateTryGet (default: true)
  • [FlyweightFactory] on factory method

    • required signature: static TValue Create(TKey key) or static ValueTask<TValue> CreateAsync(TKey key, CancellationToken ct = default)

Enums:

  • FlyweightEviction: None, Lru (v1), Ttl (v2)
  • FlyweightThreadingPolicy: SingleThreadedFast, Locking, Concurrent

Semantics (must-have)

Keying

  • Uses IEqualityComparer<TKey>.

  • If none provided:

    • use EqualityComparer<TKey>.Default.
  • For string, optional case-insensitive mode can be added later.

Creation

  • On miss, cache calls generated factory.

  • Concurrency semantics must be explicit:

    • v1 (recommended): under Locking policy, factory invoked at most once per key.

Threading policies

  • SingleThreadedFast: no locks; documented not thread-safe.
  • Locking: lock around dictionary operations; ensures single-create per key.
  • Concurrent: uses ConcurrentDictionary; creation semantics must be documented (may call factory multiple times unless guarded).

Eviction

V1:

  • None and Lru.

  • If Capacity > 0:

    • enforce capacity with deterministic eviction.
    • Lru implementation must be O(1) amortized and deterministic.

Allocation expectations

  • Executing Get should not allocate on hits.
  • Eviction bookkeeping allocates minimally and predictably.

Diagnostics (must-have)

Stable IDs, actionable:

  • PKFLY001 Type marked [Flyweight] must be partial.
  • PKFLY002 No [FlyweightFactory] method found.
  • PKFLY003 Multiple [FlyweightFactory] methods found.
  • PKFLY004 Factory method signature invalid.
  • PKFLY005 Cache type name conflicts with existing type.
  • PKFLY006 Invalid eviction configuration (e.g., Capacity=0 with Lru).

Generated Code Layout

  • ValueTypeName.Flyweight.g.cs

Determinism:

  • stable emission order by fully-qualified symbol name.

Testing Expectations

  • Same key returns same value instance/value (as appropriate for TValue).

  • Factory invoked once per key under Locking.

  • Capacity eviction is deterministic (drop oldest / least recently used).

  • Threading policy smoke tests.

  • Diagnostics:

    • missing/duplicate factory
    • invalid config

Acceptance Criteria

  • [Flyweight] works for class/struct/record class/record struct.
  • Generated cache supports Get + optional TryGet.
  • Threading policy is explicit and tested.
  • Eviction supports None and Lru (v1) with deterministic behavior.
  • Reflection-free implementation.
  • Diagnostics cover invalid usage/config.
  • Tests cover cache hits/misses, factory behavior, and eviction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions