dotnet / csharplang

The official repo for the design of the C# programming language
11.37k stars 1.02k forks source link

[Open issue]: static abstract interfaces and static classes #5783

Open stephentoub opened 2 years ago

stephentoub commented 2 years ago

With static abstract interface methods, consider:

using System;

public class C : IMethods
{
    public static void M() { }
}

internal interface IMethods
{
    static abstract void M();
}

This compiles fine. However, if I try to make C be a static class:

using System;

public static class C : IMethods
{
    public static void M() { }
}

internal interface IMethods
{
    static abstract void M();
}

it fails to compile with:

error CS0714: 'C': static classes cannot implement interfaces

Such an error arguably made sense before static abstract interface methods, as interface methods could only be instance members, and static classes can't have instance members (and even for an empty interface, you can't create instances of a static class and thus couldn't even use the interface as a typical marker used with is casts).

However, with static abstract interface methods, you can have an interface entirely composed of static abstract members, and it would logically make sense to have a static class implement that interface.

We could address this by either:

  1. Simply removing the error and allowing static classes to implement interfaces. If you try to implement one that has instance members, you won't be able to given the class is static, and you'll get errors about not fully implementing the interface.
  2. Allowing interfaces to be marked as static interface such that it can only contain static abstract members and not instance members, and then allow static classes to implement static interfaces.

One of the reasons one might want a static class implementing a static interface is to be able to then use that static class as a generic type argument. However, that is also prohibited by the language today, e.g. this:

using System;

public static class C
{
    public static void M() { }
}

class D
{
    public static void M1<T>(){}

    public static void M2() => M1<C>();
}

results in:

error CS0718: 'C': static types cannot be used as type arguments

We should also consider lifting this constraint.

If we do lift it, then it becomes possible for a generic type or method to create locals, fields, etc. of that generic type, which could be a static class. Those members or locals would then be useless, since you wouldn't have a way to create an instance of them nor invoke anything off of them. If that's something we want to avoid, we could go with option (2) above, allow static classes to be used as a generic type argument when that argument is constrained to a static interface, and then language rules around static types (extended from classes to interfaces) should naturally prevent creating such members and locals.

Design Meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes

ds5678 commented 7 months ago

Speaking of breaking changes, I think we should allow static classes to inherit from interfaces with instance members, as long as all those instance members have default implementations. This allows breaking change rules for interfaces to stay the same.

ds5678 commented 7 months ago

My concern is that by adding static interfaces that can only be implemented by static classes

Why wouldn't an instance class be able to inherit from a static interface?

jnm2 commented 7 months ago

@ds5678

However, it changes the mental model for what a static class is.

I don't think it'll have a big effect in practice. The compiler can outright tell you you won't be able to use a member with a signature that includes that type parameter when the type parameter is a static class.

Although intriguing, I don't think this should be done because changing a static class to an an instance class is not currently a breaking change.

It could potentially not be a breaking change.

Why wouldn't an instance class be able to inherit from a static interface?

This is Steve's option 2 in the original proposal. The reason to have a static keyword for interfaces would be to make that be the declaration of intent around which everything hinges, instead of dynamically recognizing that a static class could implement that interface. If this option was taken, it would be following the same rationale as not having method return types be inferred from the return statements in the body. Declarations of intent provide an easy way to see what external usages will be supported and to explicitly notice and decide before changing that support.

hamarb123 commented 4 months ago

Just thought I'd mention some uses that I'd like to be able to write:

static void X1<T>() where T : allows static => ...;
X1<Console>(); //an example type

interface I { /*some static members allowed here*/ } //"static interface?"
static class C : I { }
struct S : I { }
static void X2<T>() where T : allows static, I => ...;
X2<C>();
X2<S>();

X1 would allow for any type that is allowed today + static classes (and ref structs could also be allowed if they're not implicit with another allows). This would be extra useful for when we get generic bridges, e.g., so that we can bridge to a static interface that we check later.

X2 would allow any type that implements the interface, including static classes (since all the members of the interface are static). I think it would be a massive shame if we disallowed non-static-classes to implement them though, since any type can have static members, and I shouldn't have to move my implementation to another type if I don't think it's appropriate in my situation, just so I can allow a static class to implement it in another situation (nevermind the lack of generic specialisation, which is also an issue that would probably often encourage me to use structs anyway).