CodeChops.ImplementationDiscovery
1.2.0
See the version list below for details.
dotnet add package CodeChops.ImplementationDiscovery --version 1.2.0
NuGet\Install-Package CodeChops.ImplementationDiscovery -Version 1.2.0
<PackageReference Include="CodeChops.ImplementationDiscovery" Version="1.2.0" />
paket add CodeChops.ImplementationDiscovery --version 1.2.0
#r "nuget: CodeChops.ImplementationDiscovery, 1.2.0"
// Install CodeChops.ImplementationDiscovery as a Cake Addin #addin nuget:?package=CodeChops.ImplementationDiscovery&version=1.2.0 // Install CodeChops.ImplementationDiscovery as a Cake Tool #tool nuget:?package=CodeChops.ImplementationDiscovery&version=1.2.0
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.
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.
- No need to use slow reflection to collect implementations. 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
- Make the
base class
orinterface
whose implementations you want to discoverpartial
. - 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
: Iftrue
(default), all discovered implementations get an implementation ID property, see implementation IDs.HasSingletonImplementations
Iftrue
, the ID of all discovered implementations will be their implementation ID. It is set tofalse
by default.GenerateProxies
: Iftrue
, 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 tofalse
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
→AnimalEnum
IVehicle
→VehicleEnum
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 theDiscoveredObject
.
Examples
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.Reflection;
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
The LightResources library makes use of this library to collect all the resources (and their localizations).
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.
Global implementations
By default a global implementations enum will be generated in the root namespace of the assembly. This enum contains all discovered enums as value. This enum 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 interfaceIResource
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
ofIResource
. - The name of the
proxy enum
ends withProxyEnum
. - 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 | Versions 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. |
-
net7.0
- CodeChops.DomainModeling (>= 2.0.1)
- CodeChops.MagicEnums (>= 3.6.0)
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 |
Fixed inheritdocs of source generated proxy enum extensions and removed normal extensions generation.