Project · Memory

Audit subscription and cache ownership

Trace two real retaining paths and verify their explicit end boundary without pretending to measure collection.

All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.

Program.cs

OwnershipScenario.Run();

Models/Document.cs

public sealed record Document(string Name, byte[] Preview);

Infrastructure/ChangeFeed.cs

public sealed class ChangeFeed
{
    private event Action? Changed;
    public int ListenerCount => Changed?.GetInvocationList().Length ?? 0;
    public IDisposable Subscribe(Action handler)
    {
        Changed += handler;
        return new Subscription(this, handler);
    }
    internal void Unsubscribe(Action handler) => Changed -= handler;
    public void Publish() => Changed?.Invoke();
}

Infrastructure/Subscription.cs

public sealed class Subscription : IDisposable
{
    private ChangeFeed? feed;
    private Action? handler;
    public Subscription(ChangeFeed feed, Action handler) { this.feed = feed; this.handler = handler; }
    public void Dispose()
    {
        if (feed is null) return;
        feed.Unsubscribe(handler!);
        feed = null;
        handler = null;
    }
}

Cache/PreviewCache.cs

public sealed class PreviewCache
{
    private readonly Dictionary<string, Document> items = new(StringComparer.Ordinal);
    public int Count => items.Count;
    public void Put(Document document) => items[document.Name] = document;
    public void Clear() => items.Clear();
}

Labs/OwnershipScenario.cs

public static class OwnershipScenario
{
    public static void Run()
    {
        var feed = new ChangeFeed();
        var cache = new PreviewCache();
        var document = new Document("rune", new byte[32]);
        cache.Put(document);
        using (var subscription = feed.Subscribe(() => Console.WriteLine("seen:" + document.Name)))
        {
            Console.WriteLine($"listeners:{feed.ListenerCount};cache:{cache.Count}");
            feed.Publish();
        }
        cache.Clear();
        Console.WriteLine($"listeners:{feed.ListenerCount};cache:{cache.Count}");
        feed.Publish();
        GC.KeepAlive(document);
    }
}

OwnershipAudit.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>