Skip to content

Conversation

RexJaeschke
Copy link
Contributor

@RexJaeschke RexJaeschke commented Dec 5, 2022

Re the MS proposal: The main challenge when reading it was the (misleading) occurrence of the modifier override in numerous examples and narrative. As best as I can tell, this modifier is not actually permitted on any interface member. Instead, overriding is achieved via explicit implementation.

There is one open question, which has to do with the topic "Base Interface Invocations," for which the decision "Decided on base(N.I1<T>).M(s)" was made. However, as far as I can tell, support for this was not added to the V8 compiler. Can someone please confirm (or refute) that.

BTW, I think the proposal is misnamed: it really applies to all interface function members (properties, indexers, and events as well), not just methods. (it also allows nested types.)

Note: No spec changes are needed to support entry point Main in an interface. Any non-generic type containing a Main method with void/int return and no/one string[] parameter list is already permitted, which not only allows Main in a struct or class, but now also in an interface.

As of V7, the descriptions of the various member kinds has been located in classes.md, with augmenting and/or overriding text in structs.md, with pointers from structs.md back into classes.md, and that continues. However, now that many members kinds can have implementations in interfaces as well, I've added an intro para to the start of most member sections in classes.md, which contains forward pointers to that member kind's occurrence in structs.md and interfaces.md, as appropriate.

Fixes #982 (which reinstates abstract override.)

@RexJaeschke RexJaeschke added this to the C# 8.0 milestone Dec 5, 2022
@RexJaeschke RexJaeschke self-assigned this Dec 5, 2022
@RexJaeschke RexJaeschke marked this pull request as draft December 5, 2022 13:54
@RexJaeschke
Copy link
Contributor Author

RexJaeschke commented Dec 6, 2022

Depending how PR #680 is resolved, some tweaks might be needed with this PR.

As at 2025-05-03, that PR is still pending, and is tied to the review of this PR.

@BillWagner BillWagner force-pushed the default-interface-function-members branch from 84a1703 to caba5c7 Compare February 6, 2023 13:52
@RexJaeschke RexJaeschke added the type: feature This issue describes a new feature label Jul 22, 2023
@BillWagner BillWagner force-pushed the default-interface-function-members branch from caba5c7 to dc8d7a0 Compare September 25, 2023 20:54
@BillWagner
Copy link
Member

rebased on the latest draft-v8 on 09-23-2023

@KalleOlaviNiemitalo
Copy link
Contributor

Should the references to "C# implementations targeting the CLI" be changed somehow because the ECMA standard CLI does not support default interface members? Possible answers include:

  • Remove them; C# 8 implementations cannot target "the CLI".
  • Change them to say "targeting a CLI with extensions" or the like.
  • Keep them; a C# implementation can still target the standard CLI, although the mapping may become complex or inefficient or have restrictions (like not supporting dynamic loading of assemblies that were not provided at build time).

@RexJaeschke RexJaeschke added the Review: pending Proposal is available for review label Oct 13, 2023
@RexJaeschke RexJaeschke removed their assignment Oct 22, 2023
@RexJaeschke RexJaeschke marked this pull request as ready for review January 11, 2024 14:00
@gafter gafter self-assigned this Jan 16, 2024
@RexJaeschke
Copy link
Contributor Author

Re @KalleOlaviNiemitalo's concern about the Ecma CLI standard not supporting default interface function members, we previously made a related change to the draft-v8 Introduction, which in V7, said:

... Although Microsoft’s implementation of C# relies on CLI for library and run-time support, other implementations of C# need not, provided they support an alternate way of getting at the minimum CLI features required by this C# standard (see Annex C).

It now says:

... Although Microsoft’s implementation of C# relies on CLI for library and run-time support, other implementations of C# need not, provided they support the features and API required by this C# Standard (see Annex C).

I think that allows us to say nothing more for this feature with regard to the CLI.

@gafter
Copy link
Member

gafter commented Dec 15, 2024

I don't recall why this is assigned to me. Is it for general review, or to answer the question about base(T).M()? If the latter, see the following:

In short, the base(T).M() syntax has not been added to the language yet.

@BillWagner
Copy link
Member

@gafter

I don't recall why this is assigned to me. Is it for general review, or to answer the question about base(T).M()?

Going from memory, but I believe as a general reviewer.

@RexJaeschke
Copy link
Contributor Author

closed and reopened to get all tests to run

@RexJaeschke RexJaeschke added the meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting label Jun 10, 2025
@jskeet
Copy link
Contributor

jskeet commented Jun 11, 2025

@RexJaeschke and @BillWagner could you look at resolving the conflict?

I'm not sure whether we can realistically expect most members to have reviewed this before the meeting later today though. (I'll see whether I can find time, but I'm prioritizing #606.)

@BillWagner BillWagner force-pushed the default-interface-function-members branch from 22549fb to efb69e5 Compare June 11, 2025 13:57
@BillWagner
Copy link
Member

@RexJaeschke and @BillWagner could you look at resolving the conflict?

I'm not sure whether we can realistically expect most members to have reviewed this before the meeting later today though. (I'll see whether I can find time, but I'm prioritizing #606.)

@jskeet I did a full rebase.

My main goal in prioritizing this for today's meeting is to get it assigned to someone for next meeting. I agree that there isn't time to get it reviewed thoroughly.

@jskeet
Copy link
Contributor

jskeet commented Jun 25, 2025

@BillWagner Looking again, did we get as far as assigning it during the last meeting? We spent quite a bit of time talking about prose vs grammar for constraints, but we didn't record the next step...

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest commit.

Clarify the rules on overriding interface members with implementations, and on accessing members with an implementation in an interface.
jnm2
jnm2 previously requested changes Sep 11, 2025
@BillWagner
Copy link
Member

Thanks to @Nigel-Ecma for the updated grammar validation.

This is ready for a 2nd review. I'll watch for comments between now and our meeting, and address all those I can before the meeting.

I've updated the semantics on visibility and accessibility of members defined (not just declared) in an interface, along with the definition of "most specific override". Reviews should check the semantics both for correctness and clarify.

Open question for meeting discussion: See #681. Looking into our future, do we want to make the class, struct and interface grammar(s) more unified? If so, when? Where do we want to rely on semantic rules in text to disallow constructs that are consistent with the grammar?

Copy link
Contributor

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments - these are all I could actively find as new points of concern, but I have weak confidence in my own reviewing capability of this PR.

@jskeet
Copy link
Contributor

jskeet commented Sep 24, 2025

In the meeting, we weren't sure of the behavior of this:

class Program
{
    static void Main()
    {
        C c = new C();
        I i = c;

        c.M();
        i.M();
    }
}

interface I
{
    void M() => Console.WriteLine("I.M()");
}

class C : I
{
    public void M() => Console.WriteLine("C.M()");
}

It compiles, and prints C.M() twice. So it's fine for a class to implement an interface and "override" the default implementation, and it still uses the "override" however it's accessed.

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding comments

@jnm2 jnm2 dismissed their stale review September 24, 2025 19:40

Addressed

Edit files other than interfaces post meeting
Updates terminology from 'override' to 'implementation' for interface members, clarifies rules for hiding and explicitly implementing inherited members, and improves examples and notes for interface member access and inheritance. Adds details about the use of the 'new' modifier and compiler warnings, and refines explanations of most specific implementation in diamond inheritance scenarios.
Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed all comments from the 9/25 meeting. I'll resolve relevant conversations.

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(@BillWagner – you were making updates in parallel so some of my comments may have been preemptively dealt with.)

I will admit I ran out of steam going through all the semantic restrictions on the grammar in interfaces.md.

I haven’t checked the grammar does what is expected, or written any tests to add to the grammar checking phase – I will leave both till after this is merged.

I think this is close :-)

- An explicitly implemented function member shall not have the modifier `sealed`.

An *interface_declaration* creates a new declaration space ([§7.3](basic-concepts.md#73-declarations)), and the type parameters and *interface_member_declaration*s immediately contained by the *interface_declaration* introduce new members into this declaration space. The following rules apply to *interface_member_declaration*s:
Some declarations, such as *constant_declaration* (§15.4) have no restrictions in interfaces.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little superfluous as everything that is different is listed, but I also guess its harmless…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong feelings. I added this so that readers that compare class members with interface members and might think it was an omission.

Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.md#159-indexers)), with the following additional rules:
- *indexer_modifier* shall not include `override`.
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:

Suggested change
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed.
- An *indexer_declaration* that has an expression body or contains an accessor with a block body is `virtual`; the `virtual` modifier is not required, but is allowed.

in other clauses here expression body is not emphasised and block body is used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one comment where we crossed. They are all consistent now. You can resolve this is you're satisfied with the change.

Address review comments from Nigel on 9/25-26
Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments from edits base on Nigel's latest review

- An explicitly implemented function member shall not have the modifier `sealed`.

An *interface_declaration* creates a new declaration space ([§7.3](basic-concepts.md#73-declarations)), and the type parameters and *interface_member_declaration*s immediately contained by the *interface_declaration* introduce new members into this declaration space. The following rules apply to *interface_member_declaration*s:
Some declarations, such as *constant_declaration* (§15.4) have no restrictions in interfaces.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong feelings. I added this so that readers that compare class members with interface members and might think it was an omission.

Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.md#159-indexers)), with the following additional rules:
- *indexer_modifier* shall not include `override`.
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one comment where we crossed. They are all consistent now. You can resolve this is you're satisfied with the change.

The set of members of an interface declared in multiple parts ([§15.2.7](classes.md#1527-partial-type-declarations)) is the union of the members declared in each part. The bodies of all parts of the interface declaration share the same declaration space ([§7.3](basic-concepts.md#73-declarations)), and the scope of each member ([§7.7](basic-concepts.md#77-scopes)) extends to the bodies of all the parts.

> *Example*: Consider an interface `IA` with an implementation for a member `M`. As `M` is not abstract, outside that interface or any interface derived from it, that name is not visible. It must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA`.
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` of `IB`. These members aren't found through member lookup on a variable of type `C`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` of `IB`. These members aren't found through member lookup on a variable of type `C`.
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` or `IB`. These members aren't found through member lookup on a variable of type `C`.

- *method_modifier* shall not include `override`.
- An interface method declaration that has a block body or expression body as a *method_body* has an implementation ([§19.1](interfaces.md#191-general)), so it is *not* abstract.
- A method whose body is a semi-colon (`;`) is `abstract`; the `abstract` modifier is not required, but is allowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I intuitively refer to the presence of the semicolon as "having no body." Does the spec have an opinion yet on whether methods with semicolons have "semicolon bodies" or "no bodies"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nice thing about referring to this as "having no body" is that we can then just say "has a body" instead of saying "has a block body or expression body."

- *indexer_modifier* shall not include `override`.
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is an implementation ([§19.1](interfaces.md#191-general)), so it is *not* abstract.
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed.
- An *indexer_declaration* whose accessors are semi-colons (`;`) is `abstract`; the `abstract` modifier is not required, but is allowed.
Copy link
Contributor

@jnm2 jnm2 Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that the accessor is get;, not that the accessor is ;. The accessor has a semi-colon. Per the previous comment, could we adopt the lingo here of "whose accessors have no bodies"?

### §most-specific-implementation most specific implementation
Every interface and class shall have a most specific override for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific override*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific override.
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific implementation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding it confusing to bring in the topic of overrides at all. Do we need to?

Suggested change
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific implementation.
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the implementations appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique implementation that is more specific than every other implementation.

- The expression `base.M()` is valid only if `B` provides a definition for `M`. The expression `base.M()` is valid only if the most specific implementationmost-specific-implementation) of `M` is in a class type. To access the most specific implementation if it was defined in an interface, use the cast expression `(this as I).M()` to refer to the most specific implementationmost-specific-implementation) of `M` in `I` or an interface derived from `I`.
- If `B` contains a method `M` without the `virtual` or `override` modifier, that method hides `I.M()` rather than overriding it.
- If `B` contains a method `M` with a different parameter list, that method `M` is a distinct overload of `M`.
In a class `D`, with direct or indirect base class `B`, where `B` directly or indirectly implements interface `I` and `I` defines a method `M()`, the expression `base.M()` is valid only if the most specific implementation (§most-specific-implementation) of `M()` is in a class type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's much clearer to say that the validity of base.M() depends entirely on whether some base class defines its own member M(). Whether or not its own member M() is an implementation of some I.M() does not affect the validity of base.M() one way or another.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is again another case of: interface member names do not appear on classes and structs. An interface member is never available for lookup on a class- or struct-typed expression. A member is available for lookup on a class- or struct-typed expression if, and only if, the class or struct manually declares such a member of its own, accessibly. It may or may not be implementing an interface member in doing so.

Copy link
Contributor

@jnm2 jnm2 Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, here's an example where the expression base.M() is valid, but the most specific implementation of I.M() is not in a class type:

interface I 
{ 
    void M() => Console.WriteLine("impl");
}

class B : I
{
    public Action M => () => Console.WriteLine("Unrelated impl via a property");
}

class D : B
{
    void Example() => base.M(); // This is not I.M();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting Review: pending Proposal is available for review type: feature This issue describes a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reinstate "abstract override"
7 participants