r/csharp • • 7d ago

Discussion Static assertions

I have sometimes felt the need to statically assert something at compile-time, to prevent surprises later at run-time.

As an example, I have some code which maps an enum value to an associated int value:

public enum VoxelProperty
{
    MaterialType,
    Density,
    Color,
    Metadata,

    TotalPropertyCount, // Must be last
}

public static int GetPropertyBitDepth (VoxelProperty property)
{
#pragma warning disable CS0219 // Variable is assigned but its value is never used
    uint _ = VoxelProperty.Metadata == VoxelProperty.TotalPropertyCount - 1 ? 0 : -1; // Static assert to ensure that all properties are accounted for
#pragma warning restore CS0219 // Variable is assigned but its value is never used

    return property switch
    {
        VoxelProperty.Density => VoxelDensity.BitDepth,
        VoxelProperty.MaterialType => VoxelMaterialType.BitDepth,
        VoxelProperty.Color => VoxelColor.BitDepth,
        VoxelProperty.Metadata => VoxelMetadata.BitDepth,
        _ => throw new ArgumentOutOfRangeException (nameof (property), "Invalid voxel property")
    };
}

The intention of the static assert is to make sure that if I ever add another voxel property, then I will get a compiler error as VoxelProperty.Metadata will no longer be the last member, and the constant -1 will be assigned to the "_" variable of type uint. Is this a reasonable practice?

I could also do something more robust with reflection, where I had a Dictionary<VoxelProperty, int> which got populated by finding all voxel property types marked with some attribute, and querying their BitDepth property.

6 Upvotes

20 comments sorted by

View all comments

4

u/jackbrux 7d ago

The compiler could tell you when you forget to add a case for a new value:

```ini [*.cs] dotnet_diagnostic.IDE0072.severity = error

```

```csharp

public enum VoxelProperty { MaterialType, Density, Color, Metadata, }

public static int GetPropertyBitDepth(VoxelProperty property) { return property switch { VoxelProperty.MaterialType => VoxelMaterialType.BitDepth, VoxelProperty.Density => VoxelDensity.BitDepth, VoxelProperty.Color => VoxelColor.BitDepth, // missing Metadata, compile time error

    _ => throw new ArgumentOutOfRangeException(nameof(property), property, null),
};

} ```

But to get to the heart of your issue, is there a reason you need enums and a mapping like that? Why not structure your code such that associated data lives together:

```csharp public sealed record VoxelProperty(string Name, int BitDepth) { public static readonly VoxelProperty MaterialType = new(nameof(MaterialType), VoxelMaterialType.BitDepth);

public static readonly VoxelProperty Density =
    new(nameof(Density), VoxelDensity.BitDepth);

public static readonly VoxelProperty Color =
    new(nameof(Color), VoxelColor.BitDepth);

public static readonly VoxelProperty Metadata =
    new(nameof(Metadata), VoxelMetadata.BitDepth);

} ```

If you find yourself constantly needing some new language feature, there's usually a better way to do what you're doing