AdvancedGenericTypeConstraints.Abstractions
0.2.1
See the version list below for details.
dotnet add package AdvancedGenericTypeConstraints.Abstractions --version 0.2.1
NuGet\Install-Package AdvancedGenericTypeConstraints.Abstractions -Version 0.2.1
<PackageReference Include="AdvancedGenericTypeConstraints.Abstractions" Version="0.2.1" />
<PackageVersion Include="AdvancedGenericTypeConstraints.Abstractions" Version="0.2.1" />
<PackageReference Include="AdvancedGenericTypeConstraints.Abstractions" />
paket add AdvancedGenericTypeConstraints.Abstractions --version 0.2.1
#r "nuget: AdvancedGenericTypeConstraints.Abstractions, 0.2.1"
#:package AdvancedGenericTypeConstraints.Abstractions@0.2.1
#addin nuget:?package=AdvancedGenericTypeConstraints.Abstractions&version=0.2.1
#tool nuget:?package=AdvancedGenericTypeConstraints.Abstractions&version=0.2.1
AdvancedGenericTypeConstraints
AdvancedGenericTypeConstraints enables compile-time-like validation for generic type rules in C# by combining lightweight attributes with a Roslyn analyzer.
Why this exists
Native C# generic constraints cannot express rules like:
- the supplied type must implement
IHandleMessages<T>for someT - the supplied type must carry a specific attribute
- one generic type argument must come from an assembly whose name is derived from another type argument's assembly
This project closes that gap with:
- a small abstractions package that exposes declarative attributes
- a Roslyn analyzer package that validates supplied type arguments at compile time
Packages
Install both packages in the consuming project:
<ItemGroup>
<PackageReference Include="AdvancedGenericTypeConstraints.Abstractions" Version="0.2.1" />
<PackageReference Include="AdvancedGenericTypeConstraints.Analyzers" Version="0.2.1" PrivateAssets="all" />
</ItemGroup>
Example
using AdvancedGenericTypeConstraints;
[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceAttribute : Attribute;
public interface IHandleMessages<TMessage>;
public interface IFeatureRegistry
{
void RegisterMessageHandler<
[MustImplementOpenGeneric(typeof(IHandleMessages<>))]
[MustHaveAttribute(typeof(ServiceAttribute))]
TMessageHandler>();
void RegisterServiceContract<
[MustMatchAssemblyNameOf(nameof(TImplementation), suffix: ".Contracts")]
TService,
TImplementation>();
}
Available checks
The current API supports:
MustImplementOpenGenericAttributeMustImplementOpenGenericAttribute(Type openGenericType, bool exactlyOne)MustNotImplementOpenGenericAttributeMustHaveAttributeAttributeMustMatchAssemblyNameOfAttributeMustBeOpenGenericTypeAttribute
Diagnostic IDs
The analyzer currently emits these diagnostics:
AGTC001: required open generic type is missingAGTC002: forbidden open generic type is presentAGTC003: required open generic type is not matched exactly onceAGTC004: invalidMustImplementOpenGenericconfiguration on a generic parameterAGTC005: required attribute is missingAGTC006: assembly naming rule between two related types is violatedAGTC007:MustMatchAssemblyNameOfreferences an invalid related parameterAGTC008: aTypeargument is not an open generic type definition
Matching semantics
The analyzer validates both concrete type arguments and forwarded generic type parameters.
Open generic checks
The analyzer compares open generic type definitions, not closed constructed types.
A type counts as a match when the configured open generic type definition appears on:
- the concrete type argument itself
- any base type in its inheritance chain
- any implemented interface
If a generic method or type forwards one of its own type parameters into another constrained generic API, the
forwarded type parameter also counts as a match when it already declares an equivalent or stricter
MustImplementOpenGenericAttribute constraint.
Attribute checks
MustHaveAttributeAttribute checks whether the supplied type argument is directly annotated with the configured
attribute type. Derived attributes also satisfy the rule.
Forwarded generic type parameters are also accepted when they already declare the same
MustHaveAttributeAttribute constraint.
Assembly naming checks
MustMatchAssemblyNameOfAttribute compares simple assembly names.
You can apply it to:
- a generic type parameter
- a method parameter of type
System.Type
For a declaration like:
void RegisterServiceContract<
[MustMatchAssemblyNameOf(nameof(TImplementation), suffix: ".Contracts")]
TService,
TImplementation>();
the analyzer requires TService to come from an assembly named:
{AssemblyOf(TImplementation)} + ".Contracts"
The same rule also works for Type-based overloads when the call site passes statically analyzable values such as
typeof(SomeType):
void RegisterInProcessApi(
[MustMatchAssemblyNameOf(nameof(implementationType), suffix: ".Contracts")] Type serviceType,
Type implementationType);
registry.RegisterInProcessApi(typeof(Feature.Contracts.IService), typeof(Feature.ServiceImplementation));
You can also configure:
prefixsuffixAllowedTypesas an explicit whitelist for legacy exceptions
Forwarded generic type parameters are also accepted when they already declare an equivalent or stricter
MustMatchAssemblyNameOfAttribute constraint. This allows delegating overloads and explicit interface
implementations to pass constrained generic parameters through without needing #pragma warning disable AGTC006.
The same applies when a generic overload forwards into a Type-based overload via typeof(TService) and
typeof(TImplementation).
Open generic Type checks
MustBeOpenGenericTypeAttribute applies to method parameters of type System.Type.
It requires statically analyzable call sites to pass an open generic type definition such as typeof(IFoo<>) or
typeof(Foo<>).
Example:
void RegisterInProcessApi(
[MustBeOpenGenericType] Type serviceType,
[MustBeOpenGenericType] Type implementationType);
featureRegistry.RegisterInProcessApi(
serviceType: typeof(ISendResetSalesOrderSurchargesCommandService<>),
implementationType: typeof(SendResetSalesOrderSurchargesCommandService<>));
Example:
void RegisterServiceContract<
[MustMatchAssemblyNameOf(
nameof(TImplementation),
suffix: ".Contracts",
AllowedTypes = new Type[] { typeof(ICelestialPostService), typeof(IOrbitalEchoStore) })]
TService,
TImplementation>();
Forwarding example:
public interface IFeatureRegistry
{
IFeatureRegistry RegisterInProcessApi<
[MustMatchAssemblyNameOf(nameof(TImplementation), suffix: ".Contracts")] TService,
TImplementation>()
where TService : class
where TImplementation : class, TService;
}
public sealed class ConfiguredFeatureRegistry : IFeatureRegistry
{
public ConfiguredFeatureRegistry RegisterInProcessApi<
[MustMatchAssemblyNameOf(nameof(TImplementation), suffix: ".Contracts")] TService,
TImplementation>()
where TService : class
where TImplementation : class, TService
{
return this;
}
IFeatureRegistry IFeatureRegistry.RegisterInProcessApi<
[MustMatchAssemblyNameOf(nameof(TImplementation), suffix: ".Contracts")] TService,
TImplementation>()
{
return RegisterInProcessApi<TService, TImplementation>();
}
}
Non-goals
- no changes to the C# type system
- no runtime validation
- no replacement for native generic constraints
The solution is intentionally based on static analysis only.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.