C# 11 introduces us to a lot of improvements in the language. Out of the many key improvements, this article focuses on a hidden gem which, in fact, was one of the key foundation stone, upon which some of the key Math generic functionalities were built on - the Static Abstract Interface Members.
Way back in C# 8, the language enabled us to write static members for interfaces, which enabled us to execute code from an interface without the need for instantiating an instance. In C# 11, we take this further by introducing Static Abstract Members in interface.
Let us define an interface with static abstract members.
We have defined an interface IBird
with a single method CanFly()
, which is defined as static abstract. Now the implementors can provide a more specific implementation of the static method For example,
Each of the types Parrot
and Kiwi
now exposes a static member, CanFly()
, with specific implementation of its own. Remember these are static members and you do not need to instantiate an instance for using them in your client code.
The significance of the feature is magnified as it acts as the enabler for implementing operator overloading for types implementing the interface. We will get back to it in a bit. Before that, we want to explore another important concept.
Curiously Recurring Template Pattern
Consider the following interface IFooBar
The interface defines a static abstract member, CreateInstance
that returns a type of IFruit
. An implementation of the interface could be as follows.
The Apple
class implements the IFruit
interface. Additionally, it also implements an instance method SayHello()
. If you would want to create an instance of Apple
with the CreateInstance()
method and then invoke the SayHello()
method using the newly created instance, the code would look like the following.
Notice that you are forced to explicitly downcast the instance to the derived type. What if we could make the code more readable by passing the derived type as generic parameter of the interface. Let us modify the interface to accept a generic parameter.
We have now modified the interface to accept a generic parameter, which is used as a return type for the CreateInstance
method. We can also now modify the Apple
implementation to return a more specific type now. This removes the need for downcasting the instance in the consuming code.
However, we still have a problem. There is no constraint on the TDerieved
generic parameter of the interface and this could be anything. For example, consider the following code.
The Orange
class implements the interface IFruit<T>
, however, instead of passing a derived type of IFruit
as generic parameter, it passes int
type. This is perfectly legal, but not as desired. So how would one force the generic parameter to be actually a derived type of the interface itself. This is where Curiously Recurring Template Pattern comes into play.
Let us now rewrite the interface with the pattern.
We have now added a generic constraint that specifies that the generic pattern TDerieved
should be an implementation of IFruit<TDerieved>
. This would force the implementing classes to use a derived type of interface as generic parameter. For example, the Orange
class now would throw the following error.
The type 'int' cannot be used as type parameter 'TDerieved' in the generic type or method 'IFruit<TDerieved>'. There is no boxing conversion from 'int' to 'IFruit<int>'.
Operator Overloading in interface
Let us now head back to our discussion on operator overloading. Let us consider the following interface without any generic constraints.
The above code would throw the following error.
As the error describes, there is no way to constrain the type T
to ensure operation is supported by the type. This problem can be solved by using the Curiously Recurring Template Pattern.
To see the code in action, let us create an example implementation.
Let us look into another implementation of ISequenceGenerator<TDerieved>
, this time for generating Fibonacci numbers.
We could now use the FibonacciGenerator
using ++
operator generates a sequence
Doesn't the code look more readable? I think yes, though I agree that it depends on perspective. But the way I see it, static abstract members open up a whole new world opportunities for developers.
No comments:
Post a Comment