// Exercise 1: implement the three DI lifetimes (medium) // // A tiny container: Register(lifetime, factory) and Scope.Resolve(). // - Singleton: one instance for the whole container (cache in container.Singletons). // - Scoped: one instance per Scope (cache in the scope's _scoped dictionary). // - Transient: a new instance on every Resolve. // Scope.Dispose() disposes every IDisposable SCOPED or TRANSIENT instance it created (not singletons). // Resolving an unregistered type throws InvalidOperationException. // // Run with: dotnet run (inside a console project). using System; using System.Collections.Generic; enum Lifetime { Singleton, Scoped, Transient } class Container { public readonly Dictionary factory)> Registrations = new(); public readonly Dictionary Singletons = new(); public void Register(Lifetime life, Func factory) where T : class => Registrations[typeof(T)] = (life, s => factory(s)); public Scope CreateScope() => new Scope(this); } class Scope : IDisposable { readonly Container _c; readonly Dictionary _scoped = new(); readonly List _disposables = new(); public Scope(Container c) { _c = c; } public T Resolve() where T : class { // TODO return (T)_c.Registrations[typeof(T)].factory(this); } public void Dispose() { // TODO } } class Clock { public Guid Id = Guid.NewGuid(); } class Db : IDisposable { public bool Disposed; public void Dispose() => Disposed = true; } class Formatter { } class Repo { public Db Db; public Repo(Db db) { Db = db; } } class Program { static void Main() { int pass = 0, total = 0; void Check(string name, bool ok) { total++; if (ok) pass++; Console.WriteLine($"{(ok ? "PASS" : "FAIL")}: {name}"); } var c = new Container(); c.Register(Lifetime.Singleton, s => new Clock()); c.Register(Lifetime.Scoped, s => new Db()); c.Register(Lifetime.Transient, s => new Formatter()); c.Register(Lifetime.Scoped, s => new Repo(s.Resolve())); var s1 = c.CreateScope(); var s2 = c.CreateScope(); Check("singleton shared across scopes", ReferenceEquals(s1.Resolve(), s2.Resolve())); Check("scoped same within scope", ReferenceEquals(s1.Resolve(), s1.Resolve())); Check("scoped differs across scopes", !ReferenceEquals(s1.Resolve(), s2.Resolve())); Check("transient always new", !ReferenceEquals(s1.Resolve(), s1.Resolve())); Check("dependencies share scope", ReferenceEquals(s1.Resolve().Db, s1.Resolve())); var db1 = s1.Resolve(); s1.Dispose(); Check("scope disposes its scoped services", db1.Disposed); Check("other scope unaffected", !s2.Resolve().Disposed); bool threw = false; try { s2.Resolve(); } catch (InvalidOperationException) { threw = true; } Check("unregistered throws", threw); Console.WriteLine($"--- {pass}/{total} checks passed ---"); } }