-
Notifications
You must be signed in to change notification settings - Fork 1
Self type in generic
In C#, generic types that need to know the type that implements them have to add a redundant generic parameter, and so every class that implements that interface has to write its type multiple time even though the compiler knows it already. To avoid this problem in lotus, I propose this syntax :
public interface IInterface<this T>
{ /* interface body */ }
public class SomeClass : IInterface // No need to specify SomeClass again in the inheritance list
{ /* class body */ }
Notice the this
keyword in the generic parameter list. This indicates that, by default, the generic parameter T will be assigned to the type that implements the interface. However, just like extension methods in C#, it is still possible to specify a generic argument, just like any other generic type.
This is simply syntax sugar. Therefore, when using that feature, you need to be aware of a few things :
Self-typed generic parameters must be the first in the generic parameter declaration list [Is this really useful ? As long as we take care of conflicts]
You cannot have two types with the same signature modulo a self-typed generic parameter, or the self-typed-ness of a generic parameter. In general, that means that the signature of a type cannot be one above or below that of another containing a self-typed generic parameter. For example, it is a type error to have :
- Both
IInterface
andIInterface<this T>
, becauseSomeClass : IInterface
could mean-
SomeClass : IInterface
, as in the first type -
SomeClass : IInterface<SomeClass>
, as in the second type
-
- Both
IInterface<T>
andIInterface<this T>
, becauseSomeClass : IInterface<int>
could mean-
SomeClass : IInterface<int>
, as in the first type -
SomeClass : IInterface<int>
, as in the second type (with explicit parameter)
-
- Both
IInterface<T>
andIInterface<this T1, T2>
, becauseSomeClass : IInterface<int>
could mean-
SomeClass : IInterface<int>
, as in the first type -
SomeClass : IInterface<SomeClass, int>
, as in the second type
-
- Both
IInterface<T1, T2>
andIInterface<this T1, T2>
, becauseSomeClass : IInterface<int, string>
could mean-
SomeClass : IInterface<int, string>
, as in the first type -
SomeClass : IInterface<SomeClass, int, string>
as in the second type -
SomeClass : IInterface<int, string>
, as in the second type (with explicit parameter)
-
And repeat for every variation of IInterface<this T1, T2, ..., Tn>
conflicts with IInterface<T1, T2, ..., Tn>
and IInterface<T1, T2, ..., Tn-1>
.
Constraints of the form where T is IInterface
(using the same declaration as in the example) will be expanded to where T is IInterface<T>
, meaning that only types that implement the interface for their own type will be allowed. If this is not the desired effect, you can always explicitly specify the generic type you desired : where T is IInterface<U>
(see conflicts above)