< Summary

Information
Class: AmbientServices.DefaultAmbientServices
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/DefaultAmbientServices.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 37
Uncovered lines: 0
Coverable lines: 37
Total lines: 186
Line coverage: 100%
Branch coverage
100%
Covered branches: 30
Total branches: 30
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
CurrentDomain_AssemblyLoad(...)100%11100%
InitializeAlreadyLoadedDefaultAmbientServices()100%22100%
AllLoadedReferringTypes()100%1010100%
AddDefaultImplementation(...)100%1010100%
OnAssemblyLoad(...)100%44100%
TryFind(...)100%44100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/DefaultAmbientServices.cs

#LineLine coverage
 1using AmbientServices.Extensions;
 2using System;
 3using System.Collections.Concurrent;
 4using System.Collections.Generic;
 5using System.Collections.Immutable;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Linq;
 9using System.Reflection;
 10
 11namespace AmbientServices;
 12
 13/// <summary>
 14/// An attribute to identify classes implementing an ambient service default implementation.
 15/// </summary>
 16/// <remarks>
 17/// <pitch>The zero-registration way to supply the fallback implementation for a service interface: mark the class and e
 18/// <pledge>
 19/// Applying the attribute to a class with a parameterless constructor registers that class as the default implementatio
 20/// A default is only a fallback: an explicit assignment to <see cref="AmbientService{T}.Global"/> always takes preceden
 21/// </pledge>
 22/// When applied to a class with a parameterless constructor (public or non-public) in any assembly, causes each interfa
 23/// If another implementation has already been registered, the new one will be ignored.
 24/// The class instance implementing the service implementation will be constructed the first time it is requested, at mo
 25/// </remarks>
 26[AttributeUsage(AttributeTargets.Class)]
 27public sealed class DefaultAmbientServiceAttribute : Attribute
 28{
 29
 30    /// <summary>
 31    /// Constructs a DefaultAmbientServiceAttribute.
 32    /// </summary>
 33    public DefaultAmbientServiceAttribute()
 34    {
 35    }
 36    /// <summary>
 37    /// Constructs a DefaultAmbientServiceAttribute that is limited to the specified interface, even if other interfaces
 38    /// </summary>
 39    /// <param name="registrationInterface">A single registration interface (for CLS compliance).</param>
 40#pragma warning disable CA1019  // this constructor is only for CLS compliance--this attribute is accessible through the
 41    public DefaultAmbientServiceAttribute(Type registrationInterface)
 42#pragma warning restore CA1019
 43    {
 44        RegistrationInterfaces = ImmutableArray<Type>.Empty.Add(registrationInterface);
 45    }
 46    /// <summary>
 47    /// Constructs a DefaultAmbientServiceAttribute that is limited to the listed interfaces, even if other interfaces a
 48    /// </summary>
 49    /// <param name="registrationInterfaces">A params array of interface types to use for the registration instead of al
 50    public DefaultAmbientServiceAttribute(params Type[] registrationInterfaces)
 51    {
 52        RegistrationInterfaces = ImmutableArray<Type>.Empty.AddRange(registrationInterfaces);
 53    }
 54    /// <summary>
 55    /// Gets the interface types indicating which services are implemented by the class the attribute is applied to.
 56    /// If null, all interfaces that are directly implemented by the class should be used.
 57    /// </summary>
 58    public IReadOnlyList<Type>? RegistrationInterfaces { get; }
 59}
 60
 61/// <summary>
 62/// An internal static class that collects default ambient service implementations in every currently and subsequently l
 63/// </summary>
 64/// <remarks>
 65/// <pitch>The discovery registry behind <see cref="DefaultAmbientServiceAttribute"/>: the one place that answers "which
 66/// <pledge><see cref="TryFind"/> maps an interface type to its default implementation type or null, is thread-safe, and
 67/// <plan>A static <see cref="ConcurrentDictionary{TKey, TValue}"/> from interface type to implementation type, seeded b
 68/// </remarks>
 69internal static class DefaultAmbientServices
 70{
 71    private static readonly Assembly _ThisAssembly;
 72    private static readonly ConcurrentDictionary<Type, Type> _DefaultImplementations;
 73
 74    static DefaultAmbientServices()
 75    {
 376        _ThisAssembly = Assembly.GetExecutingAssembly();
 377        _DefaultImplementations = InitializeAlreadyLoadedDefaultAmbientServices();
 78        // start hooking into assembly loading now, but only do this ONCE
 379        AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
 380    }
 81    private static void CurrentDomain_AssemblyLoad(object? sender, AssemblyLoadEventArgs args)
 82    {
 383        Assembly assembly = args.LoadedAssembly;
 384        OnAssemblyLoad(assembly);
 385    }
 86
 87    private static ConcurrentDictionary<Type, Type> InitializeAlreadyLoadedDefaultAmbientServices()
 88    {
 389        ConcurrentDictionary<Type, Type> dictionary = new();
 390        foreach (Type type in AllLoadedReferringTypes())
 91        {
 392            AddDefaultImplementation(dictionary, type);
 93        }
 394        return dictionary;
 95    }
 96    /// <summary>
 97    /// Enumerates all the types in all currently loaded assemblies that refer to this assembly (they can't possibly hav
 98    /// </summary>
 99    /// <returns>An enumeration of <see cref="Type"/>s.</returns>
 100    private static IEnumerable<Type> AllLoadedReferringTypes()
 101    {
 3102        List<Assembly> checkedAssemblies = new();
 103        // loop through all the assemblies loaded in our AppDomain
 3104        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
 105        {
 106            // is this assembly us or does it reference us?
 3107            if (assembly == _ThisAssembly || assembly.DoesAssemblyReferDirectlyToAssembly(_ThisAssembly))
 108            {
 3109                checkedAssemblies.Add(assembly);
 3110                foreach (Type type in assembly.GetLoadableTypes())
 111                {
 3112                    yield return type;
 113                }
 114            }
 115        }
 3116        System.Diagnostics.Trace.WriteLine($"Loading Ambient Types From {string.Join(",", checkedAssemblies.Select(a => 
 3117    }
 118    /// <summary>
 119    /// Adds the default implementation for the specified interface type.
 120    /// </summary>
 121    /// <param name="dictionary">The dictionary to add to (usually <see cref="_DefaultImplementations"/>).</param>
 122    /// <param name="type">The interface type whose default implementation type is to be added.</param>
 123    private static void AddDefaultImplementation(ConcurrentDictionary<Type, Type> dictionary, Type type)
 124    {
 3125        DefaultAmbientServiceAttribute? attribute = type.GetCustomAttribute<DefaultAmbientServiceAttribute>();
 3126        if (attribute != null)
 127        {
 3128            IReadOnlyList<Type>? registrationInterfaces = attribute.RegistrationInterfaces;
 3129            if ((registrationInterfaces?.Count ?? 0) == 0)
 130            {
 3131                registrationInterfaces = type.GetInterfaces();   // this could be null if the specified type doesn't sup
 132            }
 3133            if (registrationInterfaces != null)
 134            {
 3135                foreach (Type iface in registrationInterfaces)
 136                {
 3137                    dictionary.TryAdd(iface, type);
 138                }
 139            }
 140        }
 3141    }
 142    /// <summary>
 143    /// Loads the default implementations in the specified assembly and notifies subscribers that the assembly has been 
 144    /// </summary>
 145    /// <param name="assembly">The <see cref="Assembly"/> whose default implementations are to be found and registered.<
 146    internal static void OnAssemblyLoad(Assembly assembly)
 147    {
 148        // does the being-loaded assembly reference THIS assembly?
 3149        if (assembly.DoesAssemblyReferDirectlyToAssembly(_ThisAssembly))
 150        {
 2151            System.Diagnostics.Trace.WriteLine($"Late Loading Ambient Types From {assembly.FullName}");
 152            // check every type in the being-loaded assembly to see if the type indicates a default service implementati
 2153            foreach (Type type in assembly.GetLoadableTypes())
 154            {
 2155                AddDefaultImplementation(_DefaultImplementations, type);
 156            }
 157        }
 3158    }
 159
 160    /// <summary>
 161    /// Tries to find the default implementation of the specified interface, if one exists.
 162    /// Thread-safe.
 163    /// </summary>
 164    /// <param name="iface">The <see cref="Type"/> of interface whose implementation is wanted.</param>
 165    /// <returns>The <see cref="Type"/> that implements that interface, or null if no implementation could be found.</re
 166    public static Type? TryFind(
 167#if NETCOREAPP3_0_OR_GREATER
 168        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.N
 169#endif
 170        Type iface)
 171    {
 3172        if (!iface.IsInterface) throw new ArgumentException("The specified type is not an interface type!", nameof(iface
 173        Type? impType;
 3174        if (_DefaultImplementations.TryGetValue(iface, out impType))
 175        {
 176            Debug.Assert(iface.IsAssignableFrom(impType));
 3177            return impType;
 178        }
 3179        return null;
 180    }
 181}
 182/// <summary>
 183/// An empty interface that needs to be in this assembly in order to get tested properly because the interface will be r
 184/// </summary>
 185internal interface ILateAssignmentTest
 186{ }