Open MadsTorgersen opened 3 years ago
About 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).
I would expect I could define a default implementation for static members, because
==
static operator and as default implementation for !=
just negate the return of ==
@leandromoh Then you would not mark it 'abstract'. It would be a non-abstract static interface member.
@leandromoh Then you would not mark it 'abstract'. It would be a non-abstract static interface member.
I see, great!
@leandromoh
I would expect I could define a default implementation for static members
That is mentioned under "Default implementations" and I think describes the concept of virtual static
members.
leandromoh
I would expect I could define a default implementation for static members
That is mentioned under "Default implementations" and I think describes the concept of
virtual static
members.
Yes, in LDM we discussed being able to put both virtual
and abstract
on static members. The difference there would be the same as in abstract class
es: abstract
does not have a body, virtual
does.
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. static abstract
will still require explicit implementation.
@Trayani yes. This was discussed as part of the design.
@CyrusNajmabadi
yes. [IAddable for numeric types] 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.
Is it known if the approach being considered to support this by the runtime would be a zero-cost abstraction?
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 IAddable<TThis, TRhs, TOutput>
. you need a few other features to make that less unwieldy still (default constraints, associated types, etc).
@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:
IResource
with the string CacheKey { get; }
property. However, then you could only access the key if you have an instance of the resource, so you are constrained to only putting resources into the cache, not taking them out, as then you don't have an instance yet.IResource<TKeyProvider> where TKeyProvider : KeyProvider, new()
and public abstract class KeyProvider { public abstract string Key { get; } }
. In other words, require there to exist a class whose only purpose is to specify a key for each resource.If interfaces could have abstract static properties and methods, then we could simply place static abstract string CacheKey { get; }
on IResource
and treat resources generically even when we don't have an instance available.
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?
public interface IAsyncFactory<T>
{
abstract static Task<T> CreateAsync();
}
public interface IExampleStrategy
{
abstract static bool IsEnabled(string foo);
void DoStuff(string foo);
}
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 static interface
such as a static class
and make it easier instead of all static abstract
typing for lazy people like I am :)
@zahirtezcan
Also, why not just call it as
static interface
such as astatic class
and make it easier instead of allstatic abstract
typing for lazy people like I am :)
A single interface could have both required instance and static members.
Also, why not just call it as static interface such as a static class and make it easier instead of all static abstract typing for lazy people like I am :)
A static interface
would require all members to still be declared as static
, just like static classes
do.
Also, why not just call it as static interface such as a static class and make it easier instead of all static abstract typing for lazy people like I am :)
A
static interface
would require all members to still be declared asstatic
, just likestatic classes
do.
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. Maybe it'll be additional hint for compiler to properly get rid of boxing in such cases, when you explicitly notify that you don't need any instance information while using this interface.
Something that came up in the runtime discussion https://github.com/dotnet/runtime/pull/49558 makes me wonder about the language proposal:
Will there be a syntax for referring directly to operators? int.operator+
(for example) doesn't seem to work at the moment. So I'm not sure if T.operator+
would work, too.
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 static int Add(int,int)
and call the latter for aggregate. Not ideal but this workaround is good enough i think
One thing I concern is the composite nature of many static operator
Some object can +
-
*
/
(number type)
Some object can only +
-
Some object can only +
*
(remember seeing this once, a class that can't -
or /
)
Some object can only +
(string)
Some object can only +
-
and all related type are not the same (DateTimeOffset
+
TimeSpan
return DateTimeOffset
but DateTimeOffset
- DateTimeOffset
return TimeSpan
)
And this is only little example, and not only operator, many static function in BCL of the same pattern too
Do we have a way to define these general interface?
One thing I concern is the composite nature of many static operator
Some object can
+
-
*
/
(number type) Some object can only+
-
Some object can only+
*
(remember seeing this once, a class that can't-
or/
) Some object can only+
(string) Some object can only+
-
and all related type are not the same (DateTimeOffset
+
TimeSpan
returnDateTimeOffset
butDateTimeOffset
-DateTimeOffset
returnTimeSpan
)And this is only little example, and not only operator, many static function in BCL of the same pattern too
Do we have a way to define these general interface?
I think it's better to create some types in BCL for existing primitive types too. eg. IAddable
and etc.
What use cases are there except generic math?
Generic math is an extremely rare requirement in practical applications.
What use cases are there except generic math?
For example, when you want to create something like numpy
.
What use cases are there except generic math?
Generic math is an extremely rare requirement in practical applications.
Totally and ultimately enormous
Imagine a factory method. We can define interface with static T Create<T>(any parameter)
and create a dictionary for any object that declare this static Create
function with auto creation
We can define a type that do nothing. But contains only static method that could be switch and replace. Such as json parser and type conversion. Instead of instantiate object it will just use static method directly
We can define a static property or function that describe the class itself (a string or enum for describing the class). Or forcing a class to declare object we want to use (such as dictionary of its own type)
Generally this feature would be great for create framework. There could be various possibility to do something with the class itself even without instance of that class. In the past we have to workaround with instance interface member. Which is not perfect
Static virtual methods where? In interfaces? Are you delirious?
Static virtual methods where? In interfaces? ~Are you delirious?~
It's necessary for operator abstraction.
It's necessary for operator abstraction.
Well, maybe there is a problem that can be solved with interfaces, but when you have a problem with operations, you usually thinks first about operations, not a stuff that is not related to them directly.
So, questions to ask before even starting thinking in that direction would be:
It's shoking that discussion jumped to the last point right from the first post.
@omikhailov
It's shoking that discussion jumped to the last point right from the first post.
There have been literally years of discussions around this issue, and the design has steadily evolved as more clarity has been raised on what the design is trying to achieve and what the tradeoffs are.
For example check out
https://github.com/dotnet/csharplang/discussions/164 https://github.com/dotnet/csharplang/issues/1711 https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-06-29.md https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-08.md
You've also said
Static virtual methods where? In interfaces? Are you delirious?
but have never explained why doing so is so terrible.
I'll also point out that calling someone delirious breaches the Code of Conduct. Please treat everyone with respect. Thanks!
from issue's summary
Today, instance members in interfaces are implicitly abstract (or virtual if they have a default implementation), but can optionally have an abstract (or virtual) modifier. Non-virtual instance members must be explicitly marked as sealed.
One doubt about abstract static members: will be optional the modifiers abstract/virtual? as well it is today for instance members
One doubt about abstract static members: will be optional the modifiers abstract/virtual? as well it is today for instance members
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-08.md#syntax-clashes
@333fred Thanks
I think this feature is great! One use of an interface is to represent a group of similar classes (e.g. in generic contexts) using class overrides to capture differences. So far we could not capture class static commonalities/differences via an interface but this is getting fixed!
I want to make a comment about default implementations for interface statics (discussed recently).
Forgetting about structs for a moment, my viewpoint is that interfaces are essentially abstract classes without storage. And therefore I would urge the team to consider the considerable benefits of allowing concrete classes to properly inherit default implementations of interface statics. (Providing of course there is no name clash arising from multiple inheritance.) So that, using the example from the LDM notes, _ = c != c;
just works.
I realise this breaks the existing concept that classes can't inherit interface default implementations. But this "hiding" of default implementations makes life difficult in quite a few ways. For example when calling an interface default implementation (the "base" method) when specialising it at class level. And in generic contexts you can end up with some really ugly casts/types at call-sites (here's an example I wrote where I was forced to call static method GetLocalAsync()
on ISupportsFluentGetLocal<TEntity, TPublicQuery, TPublicLocalResponse>
rather than on the so much more straightforward TEntity
).
I understand the need of adding new features to C#
But soon we will make it so convoluted as C++ (and scary for newcomers)
@viniciusjarina Please keep things civil. Gifs like that do not help teh conversation. Please treat this repo as a professional setting and remember to follow the .net code of conduct: https://dotnetfoundation.org/about/code-of-conduct
Importantly:
@CyrusNajmabadi Ok, I removed the GIF, but I am saying my honest opinion. By making C# so complex we are helping like 0.01% of the users, and moving newcomers away :(
@viniciusjarina Giving honest opinions is fine. Just please do so in a constructive fashion. Gifs like that are not conducive to being able to have positive and constructive discourse. Thanks! :)
By making C# so complex we are helping like 0.01% of the users, and moving newcomers away :(
I would not expect newcomers to need to use these features. This would just let authors write more sensible APIs that performed faster. User would simply see scenarios that they don't have today become available. For example, being able to do things like efficiently add arrays of disparate types with just a single helper.
@viniciusjarina
How productive is that statement, though? What programming languages that are still actively in use have stopped evolving and adding new features? I'd posit none of them.
@viniciusjarina
By making C# so complex we are helping like 0.01% of the users, and moving newcomers away :(
I can confidently state that this feature is totally not the case. This feature might not be handled by most user. But everyone will benefit from it with almost all framework and library will utilize this feature in some way. So many logic can be reused and standardized with this feature allowed. It allow so many workaround to possibly implemented in replace of many other feature
Not to mention this feature are not really that complex anyway
we are helping like 0.01% of the users
Interestingly, this statement is probably true. However, importantly, the small percent helped here are library/platform authors, who can use this to significantly and substantively improve things for all users. This is similar to all the ref
work. It's likely only interacted with by a small subset of users. But that subset gets enormous value from that that makes it way out to everyone else using .net.
I didn't mean to offend or cause any trouble.
I was only criticizing this need of adding dozens of new features every release of C#.
I know some of you might say this won't affect newcomers because will be a feature used by the framework.
But since is part of the language, when someone start to learn C# it will eventually hit this topic.
I guess if is a change that will be used by the Framework only, maybe shouldn't be in the language itself that will be used by millions of people.
I guess if is a change that will be used by the Framework only
It won't just be used by the framework. It will be used by library developers. They need the language to support them here so they can express these abilities, and so that the benefits are not just restricted only to the runtime.
I know some of you might say this won't affect newcomers
I do not see why this would be particularly impactful to newcomers. Anymore so than something like 'ref structs', or pointers, etc. They are very advanced topics that i would not expect newcomers to be introduced to.
it will eventually hit this topic.
If the newcomer is interested enough to get to this point in the language, i woudl say they're likely not a newcomer anymore. Rather, they're a very interested user that really wants to dive into all the complexity and power the language has available. In that case, more power to them :)
I was only criticizing this need of adding dozens of new features every release of C#. ... that will be used by millions of people.
Newcomers are one segment of our user base. As you said, we have millions of users. There are strong needs of that ecosystem and community to solve real pain points and break through limitations causing significant pain and perf problems in the library ecosystem. We have to balance these needs. And we have balanced them to slow and gradual improvement in the language. Not locking things down because we are worried this is the straw that breaks the camel's back with newcomers.
I didn't mean to offend or cause any trouble.
That's fine. Just please keep in mind the code of conduct, and remember to keep things respectful. Thanks! :)
@viniciusjarina and btw its not like lang and runtime team doesnt move things to API rather than as lang feature whenever they think feature doesnt deserve lang support. Eg. we have Unsafe
class with various methods that are possible via CLR but not via c# syntax like ref nulls. Or some of RuntimeHelpers
and the like.
For example, being able to do things like efficiently add arrays of disparate types with just a single helper.
What stops them now? If some hypothetical library author wants to sum apples with oranges, he can model this and much more in a normal object-oriented style. If this interface craziness continues, it will turn out that for the sake of few people who are not doing well with OOD, it will be allowed to rudely violate the basic principles of OOP by mixing implementation with interfaces.
What stops them now?
Efficiency.
If this interface craziness continues
There's nothing crazy here. This sort of approach has been wll trod in many other languages and domains. The idea that a type can specify this sort of behavior has been around much longer than i've been alive :)
it will be allowed to rudely violate the basic principles of OOP by mixing implementation with interfaces.
I don't know what you're describing here. It sounds like a complaint about Default-Interface-Members (already shipped), not static-abstract-members-in-interfaces (this proposal).
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:
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