CodeChops.ImplementationDiscovery 1.3.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package CodeChops.ImplementationDiscovery --version 1.3.2                
NuGet\Install-Package CodeChops.ImplementationDiscovery -Version 1.3.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeChops.ImplementationDiscovery" Version="1.3.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add CodeChops.ImplementationDiscovery --version 1.3.2                
#r "nuget: CodeChops.ImplementationDiscovery, 1.3.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install CodeChops.ImplementationDiscovery as a Cake Addin
#addin nuget:?package=CodeChops.ImplementationDiscovery&version=1.3.2

// Install CodeChops.ImplementationDiscovery as a Cake Tool
#tool nuget:?package=CodeChops.ImplementationDiscovery&version=1.3.2                

Implementation discovery

Provides easy-accessible, design-time information about implementations throughout your code. Just place an attribute on a specific base class or interface whose implementations you want to discover. A source generator will create an eum at design time that contains instances of all implementations.

Check out CodeChops projects for more projects.

Advantages

  • All implementations of a specific class or interface are centralized in one place.
  • You have a simple and navigable overview over what is implemented.
  • Does not use reflection. This improves startup time of your app.
  • Assembly-level trimming can be implemented in your libraries with ease: Implementations will not be trimmed because the enum contains a hard link to each implementation.
  • No need to manually implement logic to search for the correct implementation.
  • Members can be found in a static context, so there is no need to pass around a collection of implementations.

Usage

  1. Make the base class or interface whose implementations you want to discover partial.
  2. Place the attribute DiscoverImplementations on the declaration and optionally provide the following parameters:
    • EnumName: The name of the enum that is being generated. If not provided, it will create one for you, see enum name creation.
    • GenerateImplementationIds: If true (default), all discovered implementations get an implementation ID property, see implementation IDs.
    • HasSingletonImplementations If true, the ID of all discovered implementations will be their implementation ID. It is set to false by default.
    • GenerateProxies: If true, implementations are discovered in assemblies that reference the base class / interface that has to be discovered. This is done by creating a proxy enum in the assembly of the implementation (under the namespace of the base class / interface. It is set to false by default. For more information, see cross-assembly implementations.

Enum name creation

If no custom enum name has been provided to the attribute, a name will be created for you: The name of the base class or interface will be used without the leading 'I' (for interfaces) or trailing 'Base' for base classes. Enum will be placed at the end of the name. Examples:

  • AnimalBase will become AnimalEnum.
  • IVehiclewill become VehicleEnum.

The LightResources library makes use of this library to collect all the resources within an assemby.

The Geometry library makes use of this library to collect every StrictDirection implementation under one enum.

The Blame game engine library makes use of this library to discover implemented GameObjects.

Concepts

Implementations enum

When the 2 steps above are executed, an enum will be source generated in the same namespace as the base class / interface. The enum is inherited from ImplementationsEnum. It contains a mapping from implementation class name (the value of the enum member) to a discovered object (the value of the enum member). This enum can be used to iterate or look up members dynamically or statically, see API.

The implementations enum makes use of MagicEnums under the hood: a customizable and extendable way to implement enums. For more information, see the MagicEnums library.

Discovered object

The value of each enum member is a DiscoveredObject. This object contains the type, an instance, and a factory of the discovered implementation. It can implicitly be casted to the the concrete type of the implementation or the base class / interface.

Implementation IDs

When setting GenerateImplementationIds is enabled, each discovered implementation gets an implementation ID property when it is set to partial. A static property named ImplementationId will be generated which holds the corresponding implementation enum value. It creates an easy way to reach the implementation enum and look up members dynamically.

API

Implementation discovery makes use of MagicEnums under the hood, so the base API is the same as the MagicEnum API:

Method Description
CreateMember Creates a new discovered implementation member and returns it.
GetEnumerator Gets an enumerator over the enum members.
GetMembers Gets an enumerable over:<br/>- All enum members, or<br/>- Members of a specific value: Throws when no member has been found.
GetValues Gets an enumerable over the member values.
TryGetMembers Tries to get member(s) by value.
TryGetSingleMember Tries to get a single member by name / value.<br/>Throws when multiple members of the same value have been found.
GetSingleMember Gets a single member by name / value.<br/>Throws when not found or multiple members have been found.
GetUniqueValueCount Gets the unique member value count.
GetMemberCount Gets the member count.
GetDefaultValue Gets the default value of the enum.
GetOrCreateMember Creates a member or gets one if a member already exists.

This API can be used to search for, or create implementations at runtime. This package offers some extra methods and classes on the enum (ImplementationsEnum) and on the value (DiscoveredObject) of each member:

Member Description
Instance The instance of the discovered implementation. Don't mutate this instance as it will be used by other processes.
Type The type of the discovered implementation.
Create() Creates a new instance of the discovered object.
IsInitialized* Is false when the enum is still in static buildup and true if this is finished. This parameter can be used to detect cyclic references during buildup and act accordingly.
GetInstances()* Gets an IEnumerable over the instances of all discovered implementations.

Members marked with the * are only exposed on the ImplementationsEnum, not on the DiscoveredObject.

Examples

ImplementationDiscovery usage example

This generates the following code:

// <auto-generated />
#nullable enable
#pragma warning disable CS0109

global using CodeChops.MagicEnums;
global using System.Runtime.CompilerServices;
global using System.Runtime.Serialization;
using CodeChops.ImplementationDiscovery;
using CodeChops.MagicEnums;
using CodeChops.MagicEnums.Core;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

/// <summary>
/// Discovered implementations: <see cref="AnimalEnum"/>.
/// </summary>
public partial record class Animal 
{
	public static IImplementationsEnum<Animal> ImplementationEnum { get; } = new AnimalEnum();
}

/// <summary>
/// Discovered implementations for <see cref="Animal"/>:
/// <list type="table">
/// <item><see cref="global::Cat"/></item>
/// <item><see cref="global::Dog"/></item>
/// </list>
/// </summary>
internal partial record AnimalEnum : ImplementationsEnum<AnimalEnum, Animal>, IInitializable
{
	/// <summary>
	/// <see cref="global::Cat"/>
	/// </summary>
	public static AnimalEnum Cat { get; } = CreateMember(new DiscoveredObject<Animal>(typeof(global::Cat)));

	/// <summary>
	/// <see cref="global::Dog"/>
	/// </summary>
	public static AnimalEnum Dog { get; } = CreateMember(new DiscoveredObject<Animal>(typeof(global::Dog)));

	#region Initialization
	/// <summary>
	/// Is false when the enum is still in static buildup and true if this is finished.
	/// This parameter can be used to detect cyclic references during buildup and act accordingly.
	/// </summary>
	public new static bool IsInitialized { get; }

	static AnimalEnum()
	{
		IsInitialized = true;		
	}
	#endregion
}

internal static class AnimalEnumExtensions
{
	public static IEnumerable<Animal> GetDiscoveredObjects(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetMembers().Select(member => member.Instance);
	
	#region ForwardInstanceMethodsToStatic 
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetDefaultValue"/>
	public static DiscoveredObject<Animal> GetDefaultValue(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetDefaultValue();
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetMemberCount"/>
	public static int GetMemberCount(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetMemberCount();
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetUniqueValueCount"/>
	public static int GetUniqueValueCount(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetUniqueValueCount();
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetMembers()"/>
	public static IEnumerable<IImplementationsEnum<Animal>> GetMembers(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetMembers();
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetValues()"/>
	public static IEnumerable<DiscoveredObject<Animal>> GetValues(this AnimalEnum implementationsEnum)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetValues();
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.TryGetSingleMember(string, out Animal)"/>
	public static bool TryGetSingleMember(this AnimalEnum implementationsEnum, string memberName, [NotNullWhen(true)] out IImplementationsEnum<Animal>? member)
	{
		if (!MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.TryGetSingleMember(memberName, out var foundMember))
		{
			member = null;
			return false;
		}
	
		member = foundMember;
		return true;
	}
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetSingleMember(string)"/>
	public static IImplementationsEnum<Animal> GetSingleMember(this AnimalEnum implementationsEnum, string memberName)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetSingleMember(memberName);
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.TryGetSingleMember(DiscoveredObject, out Animal?)"/>
	public static bool TryGetSingleMember(DiscoveredObject<Animal> memberValue, [NotNullWhen(true)] out IImplementationsEnum<Animal>? member)
	{
		if (!MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.TryGetSingleMember(memberValue, out var foundMember))
		{
			member = null;
			return false;
		}
	
		member = foundMember;
		return true;
	}
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetSingleMember(DiscoveredObject)"/>
	public static IImplementationsEnum<Animal> GetSingleMember(DiscoveredObject<Animal> memberValue)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetSingleMember(memberValue);
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.TryGetMembers(DiscoveredObject, out IReadOnlyCollection{Animal}?)"/>
	public static bool TryGetMembers(this AnimalEnum implementationsEnum, DiscoveredObject<Animal> memberValue, [NotNullWhen(true)] out IReadOnlyCollection<IImplementationsEnum<Animal>>? members)
	{
		if (!MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.TryGetMembers(memberValue, out var foundMembers))
		{
			members = null;
			return false;
		}
	
		members = foundMembers;
		return true;
	}
	
	/// <inheritdoc cref="MagicEnumCore{Animal, DiscoveredObject}.GetMembers(DiscoveredObject)"/>
	public static IEnumerable<IImplementationsEnum<Animal>> GetMembers(this AnimalEnum implementationsEnum, DiscoveredObject<Animal> memberValue)
		=> MagicEnumCore<AnimalEnum, DiscoveredObject<Animal>>.GetMembers(memberValue);
	
	#endregion
}

#nullable restore

Global implementations

By default a global implementations enum will be generated in the root namespace of the assembly with the name AllDiscoveredImplementations. This enum contains all discovered enums as value and makes it easy to find base enums / interfaces whose implementations should be discovered. You can navigate to the concrete implementations using these values.

Cross-assembly implementations

Implementations can also be discovered across different assemblies, resulting in proxy eums. This can be done by enabling setting generateProxies. If this setting it set to true and the base class / interface is implemented in a different assembly, the referenced assembly will contain a proxy enum. Imagine the follow situation:

  • The package LightResources contains an interface IResource whose implementations are discoverable. This interface should be implemented by every resource.
  • Your website consumes that package and implements IResource.

What happens:

  • A proxy implementation enum is now created in the project of your website under the namespace of IResource.
  • The name of the proxy enum ends with ProxyEnum.
  • When the resource package is being called you can easily hand over your implementations using dependency injection, so the LightResources library can consume it.
Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on CodeChops.ImplementationDiscovery:

Package Downloads
CodeChops.Geometry

Contains objects and helpers to help the calculation of objects in 2D-space and time.

CodeChops.LightResources

Light and dynamic resources for your Blazor WebAssembly website.

CodeChops.Contracts

Easy use of contracts, adapters and polymorphism, using JSON.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.8.8 506 11/19/2024
1.8.7 416 9/29/2024
1.8.6 512 3/20/2023
1.8.5 387 3/10/2023
1.8.4 258 3/9/2023
1.8.3 394 3/6/2023
1.8.0 351 1/27/2023
1.7.0 354 1/22/2023
1.5.2 375 1/21/2023
1.5.1 383 1/21/2023
1.4.0 490 1/16/2023
1.3.2 329 1/10/2023
1.3.1 329 1/10/2023
1.2.0 371 1/10/2023
1.1.4 352 1/7/2023
1.1.3 353 1/7/2023
1.1.2 316 1/6/2023
1.1.1 483 1/6/2023
1.0.4 345 1/4/2023
1.0.2 356 1/4/2023
1.0.1 339 1/3/2023
1.0.0 331 1/2/2023
0.38.1 585 12/23/2022
0.20.8 468 9/17/2022
0.20.7 458 9/16/2022
0.20.5 460 9/16/2022
0.7.2 439 7/11/2022
0.7.1 618 7/11/2022
0.7.0 504 7/11/2022
0.6.9 735 7/10/2022
0.6.8 573 7/10/2022
0.6.7 443 7/10/2022
0.6.6 636 7/10/2022
0.6.5 430 7/8/2022
0.6.4 431 7/8/2022
0.6.3 421 7/7/2022
0.6.2 436 7/7/2022
0.6.1 435 7/6/2022
0.6.0 452 7/6/2022
0.5.7 472 7/5/2022
0.5.6 431 6/27/2022
0.4.5 584 6/23/2022
0.4.4 451 6/23/2022
0.4.2 716 6/21/2022
0.3.2 433 6/15/2022
0.3.1 421 6/15/2022
0.3.0 417 6/15/2022
0.2.2 420 6/14/2022
0.2.0 472 6/14/2022
0.1.9 461 6/14/2022
0.1.8 440 6/14/2022
0.1.7 432 6/14/2022
0.1.6 439 6/13/2022
0.1.5 447 6/13/2022

Don't generated proxy enum initialization code but add it to AllDiscoveredImplementations.