-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Proposal]: Static abstract members in interfaces #4436
Comments
About I would expect I could define a default implementation for static members, because
|
@leandromoh Then you would not mark it 'abstract'. It would be a non-abstract static interface member. |
I see, great! |
That is mentioned under "Default implementations" and I think describes the concept of |
Yes, in LDM we discussed being able to put both |
My understanding is that this would be only available through constrained generics. Would we somehow be able to define virtual extension methods? interface ITokenExtensions {
abstract static bool IsLiteral(this Token tk);
}
class C<T> where T : ITokenExtensions {
// ignore the fact that currently using static excludes extensions and type-level usings don't exist
using static T;
} Though I think shapes would be better suited for this case. shape SToken {
bool IsLiteral { get; }
}
implement SToken for MyToken {
bool IsLiteral { get { .. } }
} interface SToken<TThis> {
abstract static bool get_IsLiteral(TThis @this);
}
struct SToken_for_MyToken : SToken<MyToken> {
public static bool get_IsLiteral(MyToken @this) { .. }
} And kind of covers "extension everything" as well. |
It might be too soon to ask, but in case this feature gets added to the language, would it makes sense to add some general-purpose interfaces-and-their-implementations to BCL (such as IAddable for numeric types?). @alrz , I believe your example should be indeed covered by shapes which revolve around implicit implementation. |
@Trayani yes. This was discussed as part of the design. |
Is it known if the approach being considered to support this by the runtime would be a zero-cost abstraction? I understand that the BCL considered adding numeric interfaces quite some time ago but they ended up being considered unwieldy and to have too much performance overhead so they got axed. |
Couldn't that all be runtime intrinsics? So in practice all of it should compile away at runtime. Also I think the actual impl would be more involved than that. Looking at rust implementation (https://doc.rust-lang.org/src/core/ops/arith.rs.html) it could turn out to be something like |
@HaloFour I don't see why it can't be a ZCA for struct types. They get specialized copies of the generic methods, so baking the right implementation into each copy should be straightforward. |
Not having virtual/abstract static members in classes ship at the same time will present a weird scenario where you have to move static members to interfaces if you want to abstract over them. It breaks some of the symmetry present between classes/interfaces. If this feature and default implementations are too costly to design because of static virtual members calling each other, then a reasonable compromise would just be to ban them calling each other in the first version of the feature and revisit in the future if there's interest. |
I came to think of a situation where this feature would be handy, and figured I'd contribute it to the discussion as another reason to consider this feature: When writing generic methods, you sometimes face the issue that you require some information about the generic type itself (as opposed to an instance of the type), and currently there is no great way to enforce that that information exists. For instance, say you have a generic method that places some type of resource (i.e. a generic type) into a cache, and each type of resource should define a key that it should be cached under. Some options we have today:
If interfaces could have abstract static properties and methods, then we could simply place I guess my point is that having this feature would allow us to write very nice generic APIs that communicate very clearly to the client how to use them, while at the same time allowing us to write much more concise code that can deal with a broad range of types in a generic way. I therefore think this would be a very valuable addition the language. I also have a feeling that this has the potential to enable a lot of new powerful meta programming, and that's always fun. |
Want to +100 for this if possible |
IMonoid |
Just as some clarification since the opening motivational example was mostly based on operators: Will this allow interface declarations like the following?
|
As proposed, yes, you could define those abstract statics. Math may be the motivating example, but factories will also be possible. |
I do hope these don't result in boxing or virtual-calls. Otherwise using a generic method on an array of values would be a pitfall to avoid for all newcomers. Also, why not just call it as |
A single interface could have both required instance and static members. |
A |
I'd say this can be a nice addition anyway. Same as for classes, by default interfaces can contain both static and instance methods, but static interfaces can have only static methods. |
Something that came up in the runtime discussion dotnet/runtime#49558 makes me wonder about the language proposal: Will there be a syntax for referring directly to operators? Example: public static T SumAll<T> (IEnumerable<T> seq) where T: IAddable<T> {
return seq.Aggregate(T.Zero, T.operator+);
} |
@lambdageek Recommended way to implement operators was always that you provide normal method that actually implements operator and call that method in operator. So for your example you would implement operator and |
|
We're not talking about those types of members though. We're talking about static members. We already shipped static members with the semantics being that they are not virtual. We can't change that. So that means we needed a way to mark a static member as being virtual, so that required explicit syntax to distinguish the two cases. |
Hi there, In this line, M<I<C>>(); // Disallowed: I is an interface can we lighten the restriction to that as long as the abstract static method is not actually called in it, the case is allowed? I get that this check may not be perfect (that another method called in this method or class may actually use it). But in the simple case of the type ( With this, we can ask implementing classes of the interface to have that method, but still allow the original interface to act like a normal interface in other cases. |
@Kimi-Arthur see #5955 for a description of the typing hole doing so would introduce. |
Has there been any movement on adding |
That seem impossible
|
I think it could be made possible with the introduction of a But I think @TahirAhmadov meant |
I see that "static virtual" and providing a default implementation is supported, however I don't get the option to override that default behavior when implementating the interface... Is this this in the works? Or am I missing something? |
public interface IFoo<T> where T : IFoo<T>
{
static abstract Func<T, bool> ToBool { get; }
static virtual Func<T, int> ToInt { get => x => T.ToBool(x) ? 1 : 0; }
}
class C : IFoo<C>
{
public static Func<C, bool> ToBool => throw new NotImplementedException();
// > however I don't get the option to override that default behavior when implementating the interface
// This is the override of hte default behavior.
public static Func<C, int> ToInt => throw new NotImplementedException();
} |
Tnx for your quick reply... Ah.. ok... so no "override" keyword needed ... That is the part that confused me I guess since it differs from overriding virtual instance members.... and the lack of intellisence. |
@MCGPPeters Right. It's not an override. It's an impl of the interface member (which happens to also have a default impl if no impl was provided). That's never bene considered an 'override' in the DIM world (and statics don't change that). Thanks! :) |
@CyrusNajmabadi I stand corrected... I guess the point I wanted to make there is that the discoverability of static interface members with a default implementation that can be 'overridden' could be improved (at least for me :) ). When overriding a virtual instance member, IntelliSense helps me by showing which members I can override when typing 'override'. In this case, even though the static member is marked virtual, I don't have that. I would have to read the interface source file to discover this and copy the member definition and replace the generic parameters (in your sample T by C). I might not have that readily available when it is an interface from a library I don't own... Make sense? Or is there a way I am not aware of... Thanks again |
I'm waiting for this feature to appear for netstandard. |
That's never happening, @limeniye. |
Note the blog that covers this: https://devblogs.microsoft.com/dotnet/the-future-of-net-standard/ .NET Standard 2.1 was the "last" version because moving forward we just have the unified So the |
Static abstract members in interfaces
Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/static-abstracts-in-interfaces.md
Summary
An interface is allowed to specify abstract static members that implementing classes and structs are then required to provide an explicit or implicit implementation of. The members can be accessed off of type parameters that are constrained by the interface.
Motivation
There is currently no way to abstract over static members and write generalized code that applies across types that define those static members. This is particularly problematic for member kinds that only exist in a static form, notably operators.
This feature allows generic algorithms over numeric types, represented by interface constraints that specify the presence of given operators. The algorithms can therefore be expressed in terms of such operators:
Syntax
Interface members
The feature would allow static interface members to be declared virtual.
Today's rules
Today, instance members in interfaces are implicitly abstract (or virtual if they have a default implementation), but can optionally have an
abstract
(orvirtual
) modifier. Non-virtual instance members must be explicitly marked assealed
.Static interface members today are implicitly non-virtual, and do not allow
abstract
,virtual
orsealed
modifiers.Proposal
Abstract virtual members
Static interface members other than fields are allowed to also have the
abstract
modifier. Abstract static members are not allowed to have a body (or in the case of properties, the accessors are not allowed to have a body).Open question: Operators
==
and!=
as well as the implicit and explicit conversion operators are disallowed in interfaces today. Should they be allowed?Explicitly non-virtual static members
Todau's non-virtual static methods are allowed to optionally have the
sealed
modifier for symmetry with non-virtual instance members.Implementation of interface members
Today's rules
Classes and structs can implement abstract instance members of interfaces either implicitly or explicitly. An implicitly implemented interface member is a normal (virtual or non-virtual) member declaration of the class or struct that just "happens" to also implement the interface member. The member can even be inherited from a base class and thus not even be present in the class declaration.
An explicitly implemented interface member uses a qualified name to identify the interface member in question. The implementation is not directly accessible as a member on the class or struct, but only through the interface.
Proposal
No new syntax is needed in classes and structs to facilitate implicit implementation of static abstract interface members. Existing static member declarations serve that purpose.
Explicit implementations of static abstract interface members use a qualified name along with the
static
modifier.Open question: Should the qualifying
I.
go before theoperator
keyword or the operator symbol+
itself? I've chosen the former here. The latter may clash if we choose to allow conversion operators.Semantics
Operator restrictions
Today all unary and binary operator declarations have some requirement involving at least one of their operands to be of type
T
orT?
, whereT
is the instance type of the enclosing type.These requirements need to be relaxed so that a restricted operand is allowed to be of a type parameter that is constrained to
T
.Open question: Should we relax this further so that the restricted operand can be of any type that derives from, or has one of some set of implicit conversions to
T
?Implementing static abstract members
The rules for when a static member declaration in a class or struct is considered to implement a static abstract interface member, and for what requirements apply when it does, are the same as for instance members.
TBD: There may be additional or different rules necessary here that we haven't yet thought of.
Interface constraints with static abstract members
Today, when an interface
I
is used as a generic constraint, any typeT
with an implicit reference or boxing conversion toI
is considered to satisfy that constraint.When
I
has static abstract members this needs to be further restricted so thatT
cannot itself be an interface.For instance:
Accessing static abstract interface members
A static abstract interface member
M
may be accessed on a type parameterT
using the expressionT.M
whenT
is constrained by an interfaceI
andM
is an accessible static abstract member ofI
.At runtime, the actual member implementation used is the one that exists on the actual type provided as a type argument.
Drawbacks
Alternatives
Structural constraints
An alternative approach would be to have "structural constraints" directly and explicitly requiring the presence of specific operators on a type parameter. The drawbacks of that are:
- This would have to be written out every time. Having a named constraint seems better.
- This is a whole new kind of constraint, whereas the proposed feature utilizes the existing concept of interface constraints.
- It would only work for operators, not (easily) other kinds of static members.
Default implementations
An additional feature to this proposal is to allow static virtual members in interfaces to have default implementations, just as instance virtual members do. We're investigating this, but the semantics get very complicated: default implementations will want to call other static virtual members, but what syntax, semantics and implementation strategies should we use to ensure that those calls can in turn be virtual?
This seems like a further improvement that can be done independently later, if the need and the solutions arise.
Virtual static members in classes
Another additional feature would be to allow static members to be abstract and virtual in classes as well. This runs into similar complicating factors as the default implementations, and again seems like it can be saved for later, if and when the need and the design insights occur.
Unresolved questions
Called out above, but here's a list:
==
and!=
as well as the implicit and explicit conversion operators are disallowed in interfaces today. Should they be allowed?I.
in an explicit operator implenentation go before theoperator
keyword or the operator symbol (e.g.+
) itself?Design meetings
The text was updated successfully, but these errors were encountered: