From 4a5123cb859c0a5376197ce6ced132e55e8ff1cd Mon Sep 17 00:00:00 2001 From: Oleksandr Tsvirkun Date: Fri, 16 Feb 2024 21:29:30 +0200 Subject: [PATCH 1/6] Use KeyedServiceProvide instead of ContractDictionary for Splat.Microsoft.Extensions.DependencyInjection adapter --- .../MicrosoftDependencyResolver.cs | 218 +++++------------- 1 file changed, 55 insertions(+), 163 deletions(-) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs index 428f321dc..85e975cb9 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs +++ b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs @@ -17,7 +17,6 @@ namespace Splat.Microsoft.Extensions.DependencyInjection; public class MicrosoftDependencyResolver : IDependencyResolver { private const string ImmutableExceptionMessage = "This container has already been built and cannot be modified."; - private static readonly Type _dictionaryType = typeof(ContractDictionary<>); private readonly object _syncLock = new(); private IServiceCollection? _serviceCollection; private bool _isImmutable; @@ -91,7 +90,7 @@ public virtual IEnumerable GetServices(Type? serviceType, string? contra var isNull = serviceType is null; serviceType ??= typeof(NullServiceType); - IEnumerable services; + IEnumerable services = Enumerable.Empty(); if (contract is null || string.IsNullOrWhiteSpace(contract)) { @@ -99,21 +98,19 @@ public virtual IEnumerable GetServices(Type? serviceType, string? contra services = ServiceProvider.GetServices(serviceType) .Where(a => a is not null) .Select(a => a!); - - if (isNull) - { - services = services - .Cast() - .Select(nst => nst.Factory()!); - } } - else + else if (ServiceProvider is IKeyedServiceProvider serviceProvider) + { + services = serviceProvider.GetKeyedServices(serviceType, contract) + .Where(a => a is not null) + .Select(a => a!); + } + + if (isNull) { - var dic = GetContractDictionary(serviceType, false); - services = dic? - .GetFactories(contract) - .Select(f => f()!) - ?? Array.Empty(); + services = services + .Cast() + .Select(nst => nst.Factory()!); } return services; @@ -142,9 +139,10 @@ public virtual void Register(Func factory, Type? serviceType, string? c } else { - var dic = GetContractDictionary(serviceType, true); - - dic?.AddFactory(contract, factory); + _serviceCollection?.AddKeyedTransient(serviceType, contract, (_, __) => + isNull + ? new NullServiceType(factory) + : factory()!); } // required so that it gets rebuilt if not injected externally. @@ -166,7 +164,7 @@ public virtual void UnregisterCurrent(Type? serviceType, string? contract = null { if (contract is null || string.IsNullOrWhiteSpace(contract)) { - var sd = _serviceCollection?.LastOrDefault(s => s.ServiceType == serviceType); + var sd = _serviceCollection?.LastOrDefault(s => !s.IsKeyedService && s.ServiceType == serviceType); if (sd is not null) { _serviceCollection?.Remove(sd); @@ -174,14 +172,13 @@ public virtual void UnregisterCurrent(Type? serviceType, string? contract = null } else { - var dic = GetContractDictionary(serviceType, false); - if (dic is not null) + var sd = _serviceCollection?.LastOrDefault(sd => sd.IsKeyedService + && sd.ServiceKey is string serviceKey + && serviceKey == contract + && sd.ServiceType == serviceType); + if (sd is not null) { - dic.RemoveLastFactory(contract); - if (dic.IsEmpty) - { - RemoveContractService(serviceType); - } + _serviceCollection?.Remove(sd); } } @@ -208,34 +205,31 @@ public virtual void UnregisterAll(Type? serviceType, string? contract = null) lock (_syncLock) { - switch (contract) + if (_serviceCollection is null) { - case null when _serviceCollection is not null: - { - var sds = _serviceCollection - .Where(s => s.ServiceType == serviceType) - .ToList(); - - foreach (var sd in sds) - { - _serviceCollection.Remove(sd); - } - - break; - } - - case null: - throw new ArgumentException("There must be a valid contract if there is no service collection.", nameof(contract)); - default: - { - var dic = GetContractDictionary(serviceType, false); - if (dic?.TryRemoveContract(contract) == true && dic.IsEmpty) - { - RemoveContractService(serviceType); - } - - break; - } + // required so that it gets rebuilt if not injected externally. + _serviceProvider = null; + return; + } + + IEnumerable sds = Enumerable.Empty(); + + if (contract is null || string.IsNullOrWhiteSpace(contract)) + { + sds = _serviceCollection.Where(s => !s.IsKeyedService && s.ServiceType == serviceType); + } + else + { + sds = _serviceCollection + .Where(sd => sd.IsKeyedService + && sd.ServiceKey is string serviceKey + && serviceKey == contract + && sd.ServiceType == serviceType); + } + + foreach (var sd in sds.ToList()) + { + _serviceCollection.Remove(sd); } // required so that it gets rebuilt if not injected externally. @@ -255,16 +249,10 @@ public virtual bool HasRegistration(Type? serviceType, string? contract = null) { if (contract is null || string.IsNullOrWhiteSpace(contract)) { - return _serviceCollection?.Any(sd => sd.ServiceType == serviceType) == true; + return _serviceCollection?.Any(sd => !sd.IsKeyedService && sd.ServiceType == serviceType) == true; } - var dictionary = (ContractDictionary?)_serviceCollection?.FirstOrDefault(sd => sd.ServiceType == GetDictionaryType(serviceType))?.ImplementationInstance; - - return dictionary switch - { - null => false, - _ => dictionary.GetFactories(contract).Select(f => f()).Any() - }; + return _serviceCollection?.Any(sd => sd.IsKeyedService && sd.ServiceKey is string serviceKey && serviceKey == contract && sd.ServiceType == serviceType) == true; } if (contract is null) @@ -273,8 +261,12 @@ public virtual bool HasRegistration(Type? serviceType, string? contract = null) return service is not null; } - var dic = GetContractDictionary(serviceType, false); - return dic?.IsEmpty == false; + if (_serviceProvider is IKeyedServiceProvider keyedServiceProvider) + { + return keyedServiceProvider.GetKeyedService(serviceType, contract) is not null; + } + + return false; } /// @@ -291,104 +283,4 @@ public void Dispose() protected virtual void Dispose(bool disposing) { } - - private static Type GetDictionaryType(Type serviceType) => _dictionaryType.MakeGenericType(serviceType); - - private void RemoveContractService(Type serviceType) - { - var dicType = GetDictionaryType(serviceType); - var sd = _serviceCollection?.SingleOrDefault(s => s.ServiceType == serviceType); - - if (sd is not null) - { - _serviceCollection?.Remove(sd); - } - } - - [SuppressMessage("Naming Rules", "SA1300", Justification = "Intentional")] - private ContractDictionary? GetContractDictionary(Type serviceType, bool createIfNotExists) - { - var dicType = GetDictionaryType(serviceType); - - if (ServiceProvider is null) - { - throw new InvalidOperationException("The ServiceProvider is null."); - } - - if (_isImmutable) - { - return (ContractDictionary?)ServiceProvider.GetService(dicType); - } - - var dic = getDictionary(); - if (createIfNotExists && dic is null) - { - lock (_syncLock) - { - if (createIfNotExists) - { - dic = (ContractDictionary?)Activator.CreateInstance(dicType); - - if (dic is not null) - { - _serviceCollection?.AddSingleton(dicType, dic); - } - } - } - } - - return dic; - - ContractDictionary? getDictionary() => _serviceCollection? - .Where(sd => sd.ServiceType == dicType) - .Select(sd => sd.ImplementationInstance) - .Cast() - .SingleOrDefault(); - } - - private class ContractDictionary - { - private readonly ConcurrentDictionary>> _dictionary = new(); - - public bool IsEmpty => _dictionary.IsEmpty; - - public bool TryRemoveContract(string contract) => - _dictionary.TryRemove(contract, out var _); - - public Func? GetFactory(string contract) => - GetFactories(contract) - .LastOrDefault(); - - public IEnumerable> GetFactories(string contract) => - _dictionary.TryGetValue(contract, out var collection) - ? collection ?? Enumerable.Empty>() - : Array.Empty>(); - - public void AddFactory(string contract, Func factory) => - _dictionary.AddOrUpdate(contract, _ => new() { factory }, (_, list) => - { - (list ??= []).Add(factory); - return list; - }); - - public void RemoveLastFactory(string contract) => - _dictionary.AddOrUpdate(contract, [], (_, list) => - { - var lastIndex = list.Count - 1; - if (lastIndex > 0) - { - list.RemoveAt(lastIndex); - } - - // TODO if list empty remove contract entirely - // need to find how to atomically update or remove - // https://github.com/dotnet/corefx/issues/24246 - return list; - }); - } - - [SuppressMessage("Design", "CA1812: Unused class.", Justification = "Used in reflection.")] - private sealed class ContractDictionary : ContractDictionary - { - } } From a7b8f275f041de37edcb4e25b8e0952befc15652 Mon Sep 17 00:00:00 2001 From: dpvreony Date: Fri, 23 Feb 2024 20:24:29 +0000 Subject: [PATCH 2/6] refactor service contract check --- .../MicrosoftDependencyResolver.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs index 85e975cb9..27f68a1c9 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs +++ b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Data; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; namespace Splat.Microsoft.Extensions.DependencyInjection; @@ -172,10 +173,7 @@ public virtual void UnregisterCurrent(Type? serviceType, string? contract = null } else { - var sd = _serviceCollection?.LastOrDefault(sd => sd.IsKeyedService - && sd.ServiceKey is string serviceKey - && serviceKey == contract - && sd.ServiceType == serviceType); + var sd = _serviceCollection?.LastOrDefault(sd => MatchesKeyedContract(serviceType, contract, sd)); if (sd is not null) { _serviceCollection?.Remove(sd); @@ -252,7 +250,7 @@ public virtual bool HasRegistration(Type? serviceType, string? contract = null) return _serviceCollection?.Any(sd => !sd.IsKeyedService && sd.ServiceType == serviceType) == true; } - return _serviceCollection?.Any(sd => sd.IsKeyedService && sd.ServiceKey is string serviceKey && serviceKey == contract && sd.ServiceType == serviceType) == true; + return _serviceCollection?.Any(sd => MatchesKeyedContract(serviceType, contract, sd)) == true; } if (contract is null) @@ -283,4 +281,10 @@ public void Dispose() protected virtual void Dispose(bool disposing) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool MatchesKeyedContract(Type? serviceType, string contract, ServiceDescriptor sd) => + sd.ServiceType == serviceType + && sd is { IsKeyedService: true, ServiceKey: string serviceKey } + && serviceKey == contract; } From 400641e4313c3d00269c2d7bcb0cabafa92cc3b3 Mon Sep 17 00:00:00 2001 From: dpvreony Date: Fri, 23 Feb 2024 20:27:10 +0000 Subject: [PATCH 3/6] add another use of refactor, update xmldoc --- .../MicrosoftDependencyResolver.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs index 27f68a1c9..c374f12ae 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs +++ b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs @@ -191,7 +191,7 @@ public virtual void UnregisterCurrent(Type? serviceType, string? contract = null /// ignoring the argument. /// /// The service type to unregister. - /// This parameter is ignored. Service will be removed from all contracts. + /// A optional value which will remove only an object registered with the same contract. public virtual void UnregisterAll(Type? serviceType, string? contract = null) { if (_isImmutable) @@ -219,10 +219,7 @@ public virtual void UnregisterAll(Type? serviceType, string? contract = null) else { sds = _serviceCollection - .Where(sd => sd.IsKeyedService - && sd.ServiceKey is string serviceKey - && serviceKey == contract - && sd.ServiceType == serviceType); + .Where(sd => MatchesKeyedContract(serviceType, contract, sd)); } foreach (var sd in sds.ToList()) From 437c9a52d677730a2c4299385c41697c32716b19 Mon Sep 17 00:00:00 2001 From: David Vreony Date: Mon, 18 Mar 2024 17:28:03 +0000 Subject: [PATCH 4/6] add net8 as test framework --- .../Splat.Microsoft.Extensions.DependencyInjection.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection.Tests/Splat.Microsoft.Extensions.DependencyInjection.Tests.csproj b/src/Splat.Microsoft.Extensions.DependencyInjection.Tests/Splat.Microsoft.Extensions.DependencyInjection.Tests.csproj index e6117b078..3244e46eb 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection.Tests/Splat.Microsoft.Extensions.DependencyInjection.Tests.csproj +++ b/src/Splat.Microsoft.Extensions.DependencyInjection.Tests/Splat.Microsoft.Extensions.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net6.0;net8.0 $(NoWarn);1591;CA1707;SA1633;CA2000 false enable From e327002a8eb86b4c4f1a2517d60b5911775d53ea Mon Sep 17 00:00:00 2001 From: David Vreony Date: Wed, 8 May 2024 22:15:57 +0100 Subject: [PATCH 5/6] restore contract dictionary --- .../MicrosoftDependencyResolver.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs index e8d4f7a14..11c3c0e14 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs +++ b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs @@ -284,4 +284,50 @@ private static bool MatchesKeyedContract(Type? serviceType, string contract, Ser sd.ServiceType == serviceType && sd is { IsKeyedService: true, ServiceKey: string serviceKey } && serviceKey == contract; + + private class ContractDictionary + { + private readonly ConcurrentDictionary>> _dictionary = new(); + + public bool IsEmpty => _dictionary.IsEmpty; + + public bool TryRemoveContract(string contract) => + _dictionary.TryRemove(contract, out var _); + + public Func? GetFactory(string contract) => + GetFactories(contract) + .LastOrDefault(); + + public IEnumerable> GetFactories(string contract) => + _dictionary.TryGetValue(contract, out var collection) + ? collection ?? Enumerable.Empty>() + : Array.Empty>(); + + public void AddFactory(string contract, Func factory) => + _dictionary.AddOrUpdate(contract, _ => new() { factory }, (_, list) => + { + (list ??= []).Add(factory); + return list; + }); + + public void RemoveLastFactory(string contract) => + _dictionary.AddOrUpdate(contract, [], (_, list) => + { + var lastIndex = list.Count - 1; + if (lastIndex > 0) + { + list.RemoveAt(lastIndex); + } + + // TODO if list empty remove contract entirely + // need to find how to atomically update or remove + // https://github.com/dotnet/corefx/issues/24246 + return list; + }); + } + + [SuppressMessage("Design", "CA1812: Unused class.", Justification = "Used in reflection.")] + private sealed class ContractDictionary : ContractDictionary + { + } } From 7f8d1b06c9ad8cb1098ebb9a32f3b535607884fe Mon Sep 17 00:00:00 2001 From: David Vreony Date: Thu, 9 May 2024 18:46:46 +0100 Subject: [PATCH 6/6] remove contract dictionary, not actually needed --- .../MicrosoftDependencyResolver.cs | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs index 11c3c0e14..67e0e8d60 100644 --- a/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs +++ b/src/Splat.Microsoft.Extensions.DependencyInjection/MicrosoftDependencyResolver.cs @@ -3,9 +3,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using System.Collections.Concurrent; -using System.Data; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; @@ -284,50 +281,4 @@ private static bool MatchesKeyedContract(Type? serviceType, string contract, Ser sd.ServiceType == serviceType && sd is { IsKeyedService: true, ServiceKey: string serviceKey } && serviceKey == contract; - - private class ContractDictionary - { - private readonly ConcurrentDictionary>> _dictionary = new(); - - public bool IsEmpty => _dictionary.IsEmpty; - - public bool TryRemoveContract(string contract) => - _dictionary.TryRemove(contract, out var _); - - public Func? GetFactory(string contract) => - GetFactories(contract) - .LastOrDefault(); - - public IEnumerable> GetFactories(string contract) => - _dictionary.TryGetValue(contract, out var collection) - ? collection ?? Enumerable.Empty>() - : Array.Empty>(); - - public void AddFactory(string contract, Func factory) => - _dictionary.AddOrUpdate(contract, _ => new() { factory }, (_, list) => - { - (list ??= []).Add(factory); - return list; - }); - - public void RemoveLastFactory(string contract) => - _dictionary.AddOrUpdate(contract, [], (_, list) => - { - var lastIndex = list.Count - 1; - if (lastIndex > 0) - { - list.RemoveAt(lastIndex); - } - - // TODO if list empty remove contract entirely - // need to find how to atomically update or remove - // https://github.com/dotnet/corefx/issues/24246 - return list; - }); - } - - [SuppressMessage("Design", "CA1812: Unused class.", Justification = "Used in reflection.")] - private sealed class ContractDictionary : ContractDictionary - { - } }