diff --git a/README.md b/README.md index 36db6f77..922211b0 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,7 @@ dotnet run - [Exposed roots via root arg](readme/exposed-roots-via-root-arg.md) - [Exposed generic roots](readme/exposed-generic-roots.md) - [Exposed generic roots with args](readme/exposed-generic-roots-with-args.md) +- [Serilog](readme/serilog.md) ### Applications - Console - [Schrödinger's cat](readme/Console.md) diff --git a/readme/serilog.md b/readme/serilog.md new file mode 100644 index 00000000..6543754b --- /dev/null +++ b/readme/serilog.md @@ -0,0 +1,309 @@ +#### Serilog + +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/SerilogScenario.cs) + + +```c# +interface IDependency; + +class Dependency : IDependency +{ + public Dependency(ILogger log) + { + log.Information("created"); + } +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service : IService +{ + public Service( + ILogger log, + IDependency dependency) + { + Dependency = dependency; + log.Information("created"); + } + + public IDependency Dependency { get; } +} + +interface ILogger: ILogger; + +class Logger(ILogger logger) : ILogger +{ + private readonly ILogger _logger = + logger.ForContext(typeof(T)); + + public void Write(LogEvent logEvent) => + _logger.Write(logEvent); +} + +class EventSink(ICollection events) + : ILogEventSink +{ + public void Emit(LogEvent logEvent) => + events.Add(logEvent); +} + +partial class Composition +{ + private void Setup() => + DI.Setup(nameof(Composition)) + .Arg("logger") + .Bind().As(Lifetime.Singleton).To>() + + .Bind().To() + .Bind().To() + .Root(nameof(Root)); +} + +var events = new List(); +var serilogLogger = new LoggerConfiguration() + .WriteTo.Sink(new EventSink(events)) + .CreateLogger(); + +var composition = new Composition(logger: serilogLogger); +var service = composition.Root; +events.Count.ShouldBe(2); +``` + +The following partial class will be generated: + +```c# +partial class Composition +{ + private readonly Composition _root; + private readonly Lock _lock; + + private Logger? _singletonLogger47; + private Logger? _singletonLogger48; + + private readonly Serilog.ILogger _argLogger; + + [OrdinalAttribute(10)] + public Composition(Serilog.ILogger logger) + { + _argLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + _root = this; + _lock = new Lock(); + } + + internal Composition(Composition parentScope) + { + _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; + _argLogger = _root._argLogger; + _lock = _root._lock; + } + + public IService Root + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_root._singletonLogger48 is null) + { + using (_lock.EnterScope()) + { + if (_root._singletonLogger48 is null) + { + _root._singletonLogger48 = new Logger(_argLogger); + } + } + } + + if (_root._singletonLogger47 is null) + { + using (_lock.EnterScope()) + { + if (_root._singletonLogger47 is null) + { + _root._singletonLogger47 = new Logger(_argLogger); + } + } + } + + return new Service(_root._singletonLogger47!, new Dependency(_root._singletonLogger48!)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Resolve() + { + return Resolver.Value.Resolve(this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Resolve(object? tag) + { + return Resolver.Value.ResolveByTag(this, tag); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object Resolve(Type type) + { + var index = (int)(_bucketSize * ((uint)RuntimeHelpers.GetHashCode(type) % 1)); + ref var pair = ref _buckets[index]; + return pair.Key == type ? pair.Value.Resolve(this) : Resolve(type, index); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private object Resolve(Type type, int index) + { + var finish = index + _bucketSize; + while (++index < finish) + { + ref var pair = ref _buckets[index]; + if (pair.Key == type) + { + return pair.Value.Resolve(this); + } + } + + throw new InvalidOperationException($"{CannotResolveMessage} {OfTypeMessage} {type}."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object Resolve(Type type, object? tag) + { + var index = (int)(_bucketSize * ((uint)RuntimeHelpers.GetHashCode(type) % 1)); + ref var pair = ref _buckets[index]; + return pair.Key == type ? pair.Value.ResolveByTag(this, tag) : Resolve(type, tag, index); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private object Resolve(Type type, object? tag, int index) + { + var finish = index + _bucketSize; + while (++index < finish) + { + ref var pair = ref _buckets[index]; + if (pair.Key == type) + { + return pair.Value.ResolveByTag(this, tag); + } + } + + throw new InvalidOperationException($"{CannotResolveMessage} \"{tag}\" {OfTypeMessage} {type}."); + } + + private readonly static int _bucketSize; + private readonly static Pair>[] _buckets; + + static Composition() + { + var valResolver_0000 = new Resolver_0000(); + Resolver.Value = valResolver_0000; + _buckets = Buckets>.Create( + 1, + out _bucketSize, + new Pair>[1] + { + new Pair>(typeof(IService), valResolver_0000) + }); + } + + private const string CannotResolveMessage = "Cannot resolve composition root "; + private const string OfTypeMessage = "of type "; + + private class Resolver: IResolver + { + public static IResolver Value = new Resolver(); + + public virtual T Resolve(Composition composite) + { + throw new InvalidOperationException($"{CannotResolveMessage}{OfTypeMessage}{typeof(T)}."); + } + + public virtual T ResolveByTag(Composition composite, object tag) + { + throw new InvalidOperationException($"{CannotResolveMessage}\"{tag}\" {OfTypeMessage}{typeof(T)}."); + } + } + + private sealed class Resolver_0000: Resolver + { + public override IService Resolve(Composition composition) + { + return composition.Root; + } + + public override IService ResolveByTag(Composition composition, object tag) + { + switch (tag) + { + case null: + return composition.Root; + + default: + return base.ResolveByTag(composition, tag); + } + } + } +} +``` + +Class diagram: + +```mermaid +--- + config: + class: + hideEmptyMembersBox: true +--- +classDiagram + Service --|> IService + LoggerᐸServiceᐳ --|> ILoggerᐸServiceᐳ + Dependency --|> IDependency + LoggerᐸDependencyᐳ --|> ILoggerᐸDependencyᐳ + Composition ..> Service : IService Root + Service o-- "Singleton" LoggerᐸServiceᐳ : ILoggerᐸServiceᐳ + Service *-- Dependency : IDependency + LoggerᐸServiceᐳ o-- ILogger : Argument "logger" + Dependency o-- "Singleton" LoggerᐸDependencyᐳ : ILoggerᐸDependencyᐳ + LoggerᐸDependencyᐳ o-- ILogger : Argument "logger" + namespace Pure.DI.UsageTests.Advanced.SerilogScenario { + class Composition { + <> + +IService Root + + T ResolveᐸTᐳ() + + T ResolveᐸTᐳ(object? tag) + + object Resolve(Type type) + + object Resolve(Type type, object? tag) + } + class Dependency { + +Dependency(ILoggerᐸDependencyᐳ log) + } + class IDependency { + <> + } + class ILoggerᐸDependencyᐳ { + <> + } + class ILoggerᐸServiceᐳ { + <> + } + class IService { + <> + } + class LoggerᐸDependencyᐳ { + +Logger(ILogger logger) + } + class LoggerᐸServiceᐳ { + +Logger(ILogger logger) + } + class Service { + +Service(ILoggerᐸServiceᐳ log, IDependency dependency) + } + } + namespace Serilog { + class ILogger { + <> + } + } +``` + diff --git a/tests/Pure.DI.UsageTests/Advanced/SerilogScenario.cs b/tests/Pure.DI.UsageTests/Advanced/SerilogScenario.cs new file mode 100644 index 00000000..43cdc61f --- /dev/null +++ b/tests/Pure.DI.UsageTests/Advanced/SerilogScenario.cs @@ -0,0 +1,102 @@ +/* +$v=true +$p=301 +$d=Serilog +*/ + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable CheckNamespace +// ReSharper disable UnusedType.Global +// ReSharper disable ArrangeTypeModifiers +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedVariable + +// ReSharper disable UnusedTypeParameter +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedMemberInSuper.Global +#pragma warning disable CS9113 // Parameter is unread. +namespace Pure.DI.UsageTests.Advanced.SerilogScenario; + +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Xunit; + +// { +interface IDependency; + +class Dependency : IDependency +{ + public Dependency(ILogger log) + { + log.Information("created"); + } +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service : IService +{ + public Service( + ILogger log, + IDependency dependency) + { + Dependency = dependency; + log.Information("created"); + } + + public IDependency Dependency { get; } +} + +interface ILogger: ILogger; + +class Logger(ILogger logger) : ILogger +{ + private readonly ILogger _logger = + logger.ForContext(typeof(T)); + + public void Write(LogEvent logEvent) => + _logger.Write(logEvent); +} + +class EventSink(ICollection events) + : ILogEventSink +{ + public void Emit(LogEvent logEvent) => + events.Add(logEvent); +} + +partial class Composition +{ + private void Setup() => + DI.Setup(nameof(Composition)) + .Arg("logger") + .Bind().As(Lifetime.Singleton).To>() + + .Bind().To() + .Bind().To() + .Root(nameof(Root)); +} +// } + +public class Scenario +{ + [Fact] + public void Run() + { +// { + var events = new List(); + var serilogLogger = new LoggerConfiguration() + .WriteTo.Sink(new EventSink(events)) + .CreateLogger(); + + var composition = new Composition(logger: serilogLogger); + var service = composition.Root; + events.Count.ShouldBe(2); +// } + composition.SaveClassDiagram(); + } +} \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Pure.DI.UsageTests.csproj b/tests/Pure.DI.UsageTests/Pure.DI.UsageTests.csproj index fadcfdad..e2742561 100644 --- a/tests/Pure.DI.UsageTests/Pure.DI.UsageTests.csproj +++ b/tests/Pure.DI.UsageTests/Pure.DI.UsageTests.csproj @@ -15,6 +15,7 @@ +