Houtamelo.Spire
4.7.0
dotnet add package Houtamelo.Spire --version 4.7.0
NuGet\Install-Package Houtamelo.Spire -Version 4.7.0
<PackageReference Include="Houtamelo.Spire" Version="4.7.0" />
<PackageVersion Include="Houtamelo.Spire" Version="4.7.0" />
<PackageReference Include="Houtamelo.Spire" />
paket add Houtamelo.Spire --version 4.7.0
#r "nuget: Houtamelo.Spire, 4.7.0"
#:package Houtamelo.Spire@4.7.0
#addin nuget:?package=Houtamelo.Spire&version=4.7.0
#tool nuget:?package=Houtamelo.Spire&version=4.7.0
Houtamelo.Spire
Roslyn analyzers and source generators for C# type safety — struct initialization enforcement, discriminated unions, and exhaustive switch checking.
Installation
Just add the NuGet package:
dotnet add package Houtamelo.Spire
Rules
| Rule | Description |
|---|---|
| SPIRE001 | Non-empty array of [EnforceInitialization] struct produces default instances |
| SPIRE002 | [EnforceInitialization] on fieldless type has no effect |
| SPIRE003 | default(T) where T is an [EnforceInitialization] type |
| SPIRE004 | new T() on [EnforceInitialization] struct without parameterless constructor |
| SPIRE005 | Activator.CreateInstance on [EnforceInitialization] struct |
| SPIRE006 | Clearing array or span of [EnforceInitialization] type |
| SPIRE007 | Unsafe.SkipInit on [EnforceInitialization] struct |
| SPIRE008 | RuntimeHelpers.GetUninitializedObject on [EnforceInitialization] struct |
| SPIRE009 | Switch does not handle all variants of discriminated union |
| SPIRE010 | Expand wildcard to explicit variant arms (refactoring) |
| SPIRE011 | Discriminated union pattern field type mismatch |
| SPIRE012 | Discriminated union pattern field count mismatch |
| SPIRE013 | Accessing another variant's field |
| SPIRE014 | Accessing variant field without kind guard |
| SPIRE015 | Exhaustive enum switch |
| SPIRE016 | Cast to [EnforceInitialization] enum may produce invalid variant |
Discriminated Unions
The source generator provides:
Kindenum andkindproperty for variant discrimination.- Factory methods (
Shape.Circle(5.0)) andIsVariantproperties (shape.IsCircle). - Property-pattern and
Deconstructsupport forswitch. - Optional JSON serialization (System.Text.Json, Newtonsoft.Json).
- Exhaustive switch checking (SPIRE009) and field access safety (SPIRE011-014).
- 5 struct memory layouts + record unions.
Define a struct union with [DiscriminatedUnion]:
using Houtamelo.Spire;
[DiscriminatedUnion(Layout.Additive)]
partial struct Shape
{
[Variant] public static partial Shape Circle(double radius);
[Variant] public static partial Shape Square(int side);
[Variant] public static partial Shape Point();
}
Construct and match:
Shape shape = Shape.Circle(5.0);
// Property pattern
var area = shape switch
{
{ kind: Shape.Kind.Circle, radius: var r } => Math.PI * r * r,
{ kind: Shape.Kind.Square, side: var s } => s * s,
{ kind: Shape.Kind.Point } => 0,
};
// Deconstruct pattern
area = shape switch
{
(Shape.Kind.Circle, radius: var r) => Math.PI * r * r,
(Shape.Kind.Square, side: var s) => s * s,
(Shape.Kind.Point, _) => 0,
};
Record types are also supported:
[DiscriminatedUnion]
partial abstract record Shape
{
public partial record Circle(double radius) : Shape;
public partial record Square(int side) : Shape;
public partial record Point() : Shape;
}
See docs/discriminated-unions.md for the full guide.
Global Configuration
Set project-wide defaults via MSBuild properties in your .csproj (or Directory.Build.props):
<PropertyGroup>
<Spire_DU_DefaultLayout>Additive</Spire_DU_DefaultLayout>
<Spire_DU_DefaultGenerateDeconstruct>true</Spire_DU_DefaultGenerateDeconstruct>
<Spire_DU_DefaultJson>SystemTextJson</Spire_DU_DefaultJson>
<Spire_DU_DefaultJsonDiscriminator>kind</Spire_DU_DefaultJsonDiscriminator>
<Spire_EnforceExhaustivenessOnAllEnumTypes>false</Spire_EnforceExhaustivenessOnAllEnumTypes>
</PropertyGroup>
| Property | Values | Default |
|---|---|---|
Spire_DU_DefaultLayout |
Auto, Additive, Overlap, UnsafeOverlap, BoxedFields, BoxedTuple |
Auto |
Spire_DU_DefaultGenerateDeconstruct |
true, false |
true |
Spire_DU_DefaultJson |
None, SystemTextJson, NewtonsoftJson, Both |
None |
Spire_DU_DefaultJsonDiscriminator |
any string | kind |
Spire_EnforceExhaustivenessOnAllEnumTypes |
true, false |
false |
Per-attribute values override global defaults. Omitting an attribute parameter (or using ReadGlobalCfg) falls through to the global config.
EnforceExhaustiveness
The analyzer provides:
- Exhaustive switch checking over enum members or sealed subtypes (SPIRE015).
- Automatic suppression of
CS8509/CS8524for switches Spire's checker proves exhaustive — including tuples of enums, nullable enums, and unmarked enums covering all named members. No opt-in required. - Invalid enum cast detection (SPIRE016).
- Inherits all
[EnforceInitialization]rules (SPIRE001-008) — preventsdefault(T), uninitialized arrays, etc. - Works on enums, abstract classes, and interfaces.
Apply [EnforceExhaustiveness] to a type to require exhaustive switches:
using Houtamelo.Spire;
[EnforceExhaustiveness]
public abstract class Animal { }
public sealed class Dog : Animal { }
public sealed class Cat : Animal { }
// SPIRE015: switch does not handle 'Cat'
string Describe(Animal a) => a switch
{
Dog => "dog",
};
See docs/exhaustiveness.md for the full guide.
Discriminated Unions — Layout Strategy Comparison
N=1000, .NET 11.0, AMD Ryzen 9 9900X. Full results in docs/benchmark-results/.
Legend: Ctor = Construction, Prop = Property Pattern, Decon = Deconstruct
| Layout | Ctor Speed | Ctor Alloc | Prop Match | Decon Match | Decon Alloc | Copy | Generics |
|---|---|---|---|---|---|---|---|
| Additive | Fast | Moderate | Fastest | Slow (boxing) | Yes | Slow (large) | Yes |
| Overlap | Fast | Moderate | Fastest | Slow (boxing) | Yes | Slow (large) | No |
| UnsafeOverlap | Moderate | Moderate | Fastest | Slow (boxing) | Yes | Slow (large) | No |
| BoxedTuple | Fast | Low | Slow (cast) | Fast | None | Fast (16B) | Yes |
| BoxedFields | Slow | High | Slowest | Fast | None | Slowest | Yes |
| Record | Fastest | Low | Fast | Fastest | None | Fastest (8B) | Yes |
| Native (C# 15) | Fast | Low (heap) | Moderate | Moderate | None | Fast (ref) | Yes |
Choosing a layout
- Default choice:
Layout.Additive— fastest struct match, zero-alloc property access, works with generics. - Reference semantics or JSON:
record— fastest construction, best JSON deserialization, natural C# pattern matching. Trades GC pressure for speed. - No generics, unmanaged-heavy:
Layout.Overlap— true field overlap for minimal struct size. Cannot handle generic type parameters. - Skewed variant distribution (80%+ one variant):
Layout.BoxedTuple— fieldless fast-path is sub-nanosecond. Poor uniform performance. - C# 15 native unions: comparable to Additive/Record depending on usage; avoids source generator overhead.
- Avoid:
Layout.BoxedFields— outperformed by Additive in most categories. Only advantage: zero-alloc Deconstruct (fields already boxed).
License
| 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.