Why add static abstract members?
When you have an interface declared on a class it tells you that every instance of that class has the members that are declared in the interface. In that way the compiler knows what members you can use on any of the instances of the given type. Besides the members of the object you can also define static members on a class. Those are the members that will be available on the type and not on the instance of the class. Until now an interface implementation did not have anything to do with those static members. This changes with static abstract members. With this feature it will be possible to define that the type of a class that implements the interface must have certain static members. This can be very useful in the case of operators (operators in C# must be declared static). This is also the main driver for this feature. In .NET 7 Generic Math will be introduced and this is heavily dependent on static abstract members. Let’s take a look at an example:
|
|
In the example above you see a method that accepts a list of integers. All the integers in the list will be added and the result will be returned. Now imagine that you also want this method to work with other numeric types like doubles, floats and longs. My first thought would be to make the method generic:
|
|
Now the parameter is generic and I initialized the result variable to the default of it’s type. The example above however won’t compile because the compiler doesn’t know if the type T has a + operator implemented. This is now the only thing blocking the generic implementation. So if you want to realize this in the current C# version you have to make an own implementation of AddNumbers for every numeric type. This would add a lot of the same code with only the type being different each time. Generic Math in .NET 7 solves this by relying on static abstract members.
Generic math in .NET 7 and the use of static abstract members
In .NET 7 a new interface INumber
|
|
As you can see it has a lot of static abstract members that all the numeric types in .NET implement. Next to that it also inherits from a lot of other new interfaces that all declare static abstract members that are implemented by the different numeric .NET types. Take the IUnaryPlusOperators<TSelf, TSelf> interface for example. This interface adds the + operator. It is declared on a separate interface so that you can also reuse that for other types that implement a + operator. The IUnaryPlusOperators<TSelf, TSelf> looks like this:
|
|
With all those interfaces added a new and straightforward implementation of our AddNumbers method becomes available:
|
|
All the method calls to the AddNumbers method will work now. Next to the INumber
|
|
Using the IParsable
The same also works for properties. For example in .NET all numeric types have static properties One an Zero. Those properties can be accessed and used on the type like any normal static member. For example I could change the previous example. In that example we initialized the result to the default of the given type. In the next example the result will be initialized with the Zero property of the generic (INumber<T>)type.
|
|
Just one more example. Next to the INumber<TSelf> there is also an interface IMinMaxValue<T>. This interface exposes the static members MinValue and MaxValue that almost all numeric types have. It is in a separate interface because not all numeric types implement this (BigInteger for example does not implement this). The following example shows how the MaxValue of different types is written to the console using one generic method.
|
|
Why not just static members?
You could ask why is it needed to make a member static and abstract? My first thought was that static would be enough since you want to tell that the member should not be on the object instance of the class but be a static member on the type. The fact that it is not implemented this way is because in C# 8.0 another feature Static interface members was added to the language. This feature makes it possible to add method static implementations to your interface (not the best addition to the language in my opinion). You can for example define the following interface:
|
|
With this feature static members where already taken on interfaces. That is why this feature uses static abstract to indicate that the member should be implemented on the type.
How can it be useful in your own code / types?
Generic Math is in my opinion a very good use case where static abstract members can be used. This is mainly because operators in C# are always static and because all numeric types share the fact that they have things like a Zero, a One and stuff like calculating an average. Those things are static for a reason. They could have been added as instance properties / methods but that would be a waste of memory resources since stuff like Zero and One are the same for all instances. So this is a perfect example where it is handy that an interface defines that a type should have certain members where the compiler can rely on.
Having said that I don’t have a good example for my own code where I would use this feature now. But when you have a scenario with static members shared across types and you need to handle them in the same way this is a way to make your code cleaner (just like Microsoft did with generic math). If you have a good scenario for this let me know in the comments.
Conclusion
Looking at the generic math implementation using static abstract members I think it has already proven to be a very useful language feature. The addition of interfaces like INumber<T> (but also stuff like IParsable