In this post, let's have a look at a nice new C# language feature: Closed Class Hierarchies that's shipping as part of C# 15.
I am using .NET 11 Preview 5 (latest as of today, it will change) for
this post.
You will also want to set the following in your project file to opt
into the latest language features.
<LangVersion>preview</LangVersion>
Let's start with the problem first. Say I have a simple type hierarchy of
shapes and a switch expression that calculates the area.
Shape[] shapes = [ new Circle(2), new Rectangle(3, 4) ]; foreach (Shape shape in shapes) { Console.WriteLine($"{shape.GetType().Name}: {Area(shape):0.00}"); } static double Area(Shape shape) => shape switch { Circle(var r) => Math.PI * r * r, Rectangle(var w, var h) => w * h }; public abstract record class Shape; public record class Circle(double Radius) : Shape; public record class Rectangle(double Width, double Height) : Shape;
Here I am handling Circle and Rectangle, which are the only
two subtypes of Shape that exist. But the compiler still gives me a
warning.
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
For example, the pattern '_' is not covered.
The reason is, even though I have handled every subtype that exists today,
the compiler has no way of knowing that. Someone could derive another type
from Shape anywhere, so the compiler insists that I add a
_ (discard) arm to be safe. And that's exactly the problem: the
moment I add a _ => throw ... catch-all to silence this, I lose
all compiler help. If I add a new subtype later and forget to handle it, the
code happily compiles and falls into the catch-all at runtime.
This is where Closed class hierarchies come in. I just need to mark
the base type with the new closed keyword.
// Omitted for brevity public closed record class Shape; public record class Circle(double Radius) : Shape; public record class Rectangle(double Width, double Height) : Shape;
Now the CS8509 warning is gone, and notice I didn't have to add a
_ arm at all.
A closed type can only be directly derived from within the same
assembly, and it is implicitly abstract. Now the compiler knows the
complete set of subtypes, so it can prove the switch expression is
exhaustive.
And here is the best part. Let's say the requirements grow and I add a new
shape, Triangle, but I forget to update the Area switch.
// Omitted for brevity public closed record class Shape; public record class Circle(double Radius) : Shape; public record class Rectangle(double Width, double Height) : Shape; public record class Triangle(double Base, double Height) : Shape;
Because Shape is closed, the compiler immediately tells me
exactly what I missed.
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
For example, the pattern 'Triangle' is not covered.
Notice the difference: instead of the vague '_' is not covered, it
now says 'Triangle' is not covered, naming the exact subtype I
forgot, on every switch I need to update.
One thing while this is still in preview. The closed keyword needs a
compiler-required attribute that the BCL doesn't ship yet, so you have to
hand-roll it. If you don't, you'll get an error like this:
error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.ClosedAttribute..ctor'
Interestingly, the well-known member name drifted across preview toolsets.
The .NET SDK CLI compiler (I am on 11.0.100-preview.5) asks for
ClosedAttribute, while my Visual Studio toolset asks for
IsClosedTypeAttribute. To keep both happy, I just declared both in a
separate file.
namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ClosedAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class IsClosedTypeAttribute : Attribute { }
This is preview behavior and should get cleaned up as the feature
stabilizes, but it's good to know if you want to try it out today.
Hope this helps.
Happy Coding.
Regards,
Jaliya
No comments:
Post a Comment