{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/csharplang/issues/406","repository_url":"https://api.github.com/repos/dotnet/csharplang","labels_url":"https://api.github.com/repos/dotnet/csharplang/issues/406/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/csharplang/issues/406/comments","events_url":"https://api.github.com/repos/dotnet/csharplang/issues/406/events","html_url":"https://github.com/dotnet/csharplang/issues/406","id":219395328,"node_id":"MDU6SXNzdWUyMTkzOTUzMjg=","number":406,"title":"Issues in Default Interface Methods","user":{"login":"gafter","id":3804346,"node_id":"MDQ6VXNlcjM4MDQzNDY=","avatar_url":"https://avatars3.githubusercontent.com/u/3804346?v=4","gravatar_id":"","url":"https://api.github.com/users/gafter","html_url":"https://github.com/gafter","followers_url":"https://api.github.com/users/gafter/followers","following_url":"https://api.github.com/users/gafter/following{/other_user}","gists_url":"https://api.github.com/users/gafter/gists{/gist_id}","starred_url":"https://api.github.com/users/gafter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gafter/subscriptions","organizations_url":"https://api.github.com/users/gafter/orgs","repos_url":"https://api.github.com/users/gafter/repos","events_url":"https://api.github.com/users/gafter/events{/privacy}","received_events_url":"https://api.github.com/users/gafter/received_events","type":"User","site_admin":false},"labels":[{"id":537024200,"node_id":"MDU6TGFiZWw1MzcwMjQyMDA=","url":"https://api.github.com/repos/dotnet/csharplang/labels/Discussion","name":"Discussion","color":"c2e0c6","default":false},{"id":513192173,"node_id":"MDU6TGFiZWw1MTMxOTIxNzM=","url":"https://api.github.com/repos/dotnet/csharplang/labels/Proposal","name":"Proposal","color":"fbca04","default":false}],"state":"open","locked":false,"assignee":{"login":"gafter","id":3804346,"node_id":"MDQ6VXNlcjM4MDQzNDY=","avatar_url":"https://avatars3.githubusercontent.com/u/3804346?v=4","gravatar_id":"","url":"https://api.github.com/users/gafter","html_url":"https://github.com/gafter","followers_url":"https://api.github.com/users/gafter/followers","following_url":"https://api.github.com/users/gafter/following{/other_user}","gists_url":"https://api.github.com/users/gafter/gists{/gist_id}","starred_url":"https://api.github.com/users/gafter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gafter/subscriptions","organizations_url":"https://api.github.com/users/gafter/orgs","repos_url":"https://api.github.com/users/gafter/repos","events_url":"https://api.github.com/users/gafter/events{/privacy}","received_events_url":"https://api.github.com/users/gafter/received_events","type":"User","site_admin":false},"assignees":[{"login":"gafter","id":3804346,"node_id":"MDQ6VXNlcjM4MDQzNDY=","avatar_url":"https://avatars3.githubusercontent.com/u/3804346?v=4","gravatar_id":"","url":"https://api.github.com/users/gafter","html_url":"https://github.com/gafter","followers_url":"https://api.github.com/users/gafter/followers","following_url":"https://api.github.com/users/gafter/following{/other_user}","gists_url":"https://api.github.com/users/gafter/gists{/gist_id}","starred_url":"https://api.github.com/users/gafter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gafter/subscriptions","organizations_url":"https://api.github.com/users/gafter/orgs","repos_url":"https://api.github.com/users/gafter/repos","events_url":"https://api.github.com/users/gafter/events{/privacy}","received_events_url":"https://api.github.com/users/gafter/received_events","type":"User","site_admin":false}],"milestone":null,"comments":92,"created_at":"2017-04-04T21:50:47Z","updated_at":"2018-10-12T12:29:29Z","closed_at":null,"author_association":"MEMBER","body":"The following notes are intended to prime the pump for LDM discussion as soon as the LDM has time to take up these issues.\r\n\r\n\r\nIssues in [Default Interface Methods](https://github.com/dotnet/csharplang/issues/52)\r\n=======================\r\n\r\n### Event accessors\r\n\r\n> ***Closed Issue:*** Can an event be overridden \"piecewise\"?\r\n\r\nConsider this case:\r\n\r\n``` c#\r\npublic interface I1\r\n{\r\n    event T e1;\r\n}\r\npublic interface I2 : I1\r\n{\r\n    override event T\r\n    {\r\n        add { }\r\n        // error: \"remove\" accessor missing\r\n    } \r\n}\r\n```\r\n\r\nThis \"partial\" implementation of the event is not permitted because, as in a class, the syntax for an event declaration does not permit only one accessor; both (or neither) must be provided. You could accomplish the same thing by permitting the abstract remove accessor in the syntax to be implicitly abstract by the absence of a body:\r\n\r\n``` c#\r\npublic interface I1\r\n{\r\n    event T e1;\r\n}\r\npublic interface I2 : I1\r\n{\r\n    override event T\r\n    {\r\n        add { }\r\n        remove; // implicitly abstract\r\n    } \r\n}\r\n```\r\n\r\nNote that *this is a new (proposed) syntax*. In the current grammar, event accessors have a mandatory body.\r\n\r\n> ***Closed Issue:*** Can an event accessor be (implicitly) abstract by the omission of a body, similarly to the way that methods in interfaces and property accessors are (implicitly) abstract by the omission of a body?\r\n\r\n***Decision:*** (2017-04-18) No, event declarations require both concrete accessors (or neither).\r\n\r\n### Reabstraction in a Class\r\n\r\n***Closed Issue:*** We should confirm that this is permitted (otherwise adding a default implementation would be a breaking change):\r\n\r\n``` c#\r\ninterface I1\r\n{\r\n    void M() { }\r\n}\r\nabstract class C : I1\r\n{\r\n    public abstract void M(); // implement I1.M with an abstract method in C\r\n}\r\n```\r\n\r\n***Decision:*** (2017-04-18) Yes, adding a body to an interface member declaration shouldn't break C.\r\n\r\n### Sealed Override\r\n\r\nThe previous question implicitly assumes that the `sealed` modifier can be applied to an `override` in an interface. This contradicts the draft specification. Do we want to permit sealing an override? Source and binary compatibility effects of sealing should be considered.\r\n\r\n> ***Closed Issue:*** Should we permit sealing an override?\r\n\r\n***Decision:*** (2017-04-18) Let's not allowed `sealed` on overrides in interfaces. The only use of `sealed` on interface members is to make them non-virtual in their initial declaration.\r\n\r\n### Diamond inheritance and classes\r\n\r\nThe draft of the proposal prefers class overrides to interface overrides in diamond inheritance scenarios:\r\n\r\n> We require that every interface and class have a *most specific override* for every interface method 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 method itself is considered the most specific override.\r\n> \r\n> One override `M1` is considered *more specific* than another override `M2` if `M1` is declared on type `T1`, `M2` is declared on type `T2`, and either\r\n> 1. `T1` contains `T2` among its direct or indirect interfaces, or\r\n> 2. `T2` is an interface type but `T1` is not an interface type.\r\n\r\nThe scenario is this\r\n\r\n``` c#\r\ninterface IA\r\n{\r\n    void M();\r\n}\r\ninterface IB : IA\r\n{\r\n    override void M() { WriteLine(\"IB\"); }\r\n}\r\nclass Base : IA\r\n{\r\n    void IA.M() { WriteLine(\"Base\"); }\r\n}\r\nclass Derived : Base, IB // allowed?\r\n{\r\n    static void Main()\r\n    {\r\n        Ia a = new Derived();\r\n        a.M();           // what does it do?\r\n    }\r\n}\r\n```\r\n\r\nWe should confirm this behavior (or decide otherwise)\r\n\r\n> ***Closed Issue:*** Confirm the draft spec, above, for *most specific override* as it applies to mixed classes and interfaces (a class takes priority over an interface). See https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes\r\n\r\n\r\n### Interface methods vs structs\r\n\r\nThere are some unfortunate interactions between default interface methods and structs.\r\n\r\n``` c#\r\ninterface IA\r\n{\r\n    public void M() { }\r\n}\r\nstruct S : IA\r\n{\r\n}\r\n```\r\n\r\nNote that interface members are not inherited:\r\n\r\n``` c#\r\nvar s = default(S);\r\ns.M(); // error: 'S' does not contain a member 'M'\r\n```\r\n\r\nConsequently, the client must box the struct to invoke interface methods\r\n\r\n``` c#\r\nIA s = default(S); // an S, boxed\r\ns.M(); // ok\r\n```\r\n\r\nBoxing in this way defeats the principal benefits of a `struct` type. Moreover, any mutation methods will have no apparent effect, because they are operating on a *boxed copy* of the struct:\r\n\r\n``` c#\r\ninterface IB\r\n{\r\n    public void Increment() { P += 1; }\r\n    public int P { get; set; }\r\n}\r\nstruct T : IB\r\n{\r\n    public int P { get; set; } // auto-property\r\n}\r\n\r\nT t = default(T);\r\nConsole.WriteLine(t.P); // prints 0\r\n(t as IB).Increment();\r\nConsole.WriteLine(t.P); // prints 0\r\n```\r\n\r\n> ***Closed Issue:*** What can we do about this:\r\n> 1. Forbid a `struct` from inheriting a default implementation. All interface methods would be treated as abstract in a `struct`. Then we may take time later to decide how to make it work better.\r\n> 2. Come up with some kind of code generation strategy that avoids boxing. Inside a method like `IB.Increment`, the type of `this` would perhaps be akin to a type parameter constrained to `IB`. In conjunction with that, to avoid boxing in the caller, non-abstract methods would be inherited from interfaces. This may increase compiler and CLR implementation work substantially.\r\n> 3. Not worry about it and just leave it as a wart.\r\n> 4. Other ideas?\r\n\r\n***Decision:*** Not worry about it and just leave it as a wart. See https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations\r\n\r\n### Base interface invocations\r\n\r\nThe draft spec suggests a syntax for base interface invocations inspired by Java: `Interface.base.M()`. We need to select a syntax, at least for the initial prototype. My favorite is `base<Interface>.M()`.\r\n\r\n> ***Closed Issue:*** What is the syntax for a base member invocation?\r\n\r\n***Decision:*** The syntax is `base(Interface).M()`. See https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. The interface so named must be a base interface, but does not need to be a direct base interface.\r\n\r\n> ***Open Issue:*** Should base interface invocations be permitted in class members?\r\n\r\n\r\n### Overriding non-public interface members\r\n\r\nIn an interface, non-public members from base interfaces are overridden using the `override` modifier. If it is an \"explicit\" override that names the interface containing the member, the access modifier is omitted.\r\n\r\n> ***Closed Issue:*** If it is an \"implicit\" override that does not name the interface, does the access modifier have to match?\r\n\r\n***Decision:*** Only public members may be implicitly overridden, and the access must match. See https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list\r\n\r\n> ***Open Issue:*** Is the access modifier required, optional, or omitted on an explicit override such as `override void IB.M() {}`? \r\n\r\n> ***Open Issue:*** Is `override` required, optional, or omitted on an explicit override such as `void IB.M() {}`? \r\n\r\nHow does one implement a non-public interface member in a class? Perhaps it must be done explicitly?\r\n``` c#\r\ninterface IA\r\n{\r\n    internal void MI();\r\n    protected void MP();\r\n}\r\nclass C : IA\r\n{\r\n    // are these implementations?\r\n    internal void MI() {}\r\n    protected void MP() {}\r\n}\r\n```\r\n\r\n> ***Closed Issue:*** How does one implement a non-public interface member in a class? \r\n\r\n***Decision:*** You can only implement non-public interface members explicitly. See https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list\r\n\r\n### Binary Compatibility 2\r\n\r\nConsider the following code in which each type is in a separate assembly\r\n\r\n``` c#\r\ninterface I1\r\n{\r\n    void M() { Impl1 }\r\n}\r\ninterface I2 : I1\r\n{\r\n    override void M() { Impl2 }\r\n}\r\ninterface I3 : I1\r\n{\r\n}\r\nclass C : I2, I3\r\n{\r\n}\r\n```\r\n\r\nWe understand that the implementation of `I1.M` in `C` is `I2.M`. What if the assembly containing `I3` is changed as follows and recompiled\r\n\r\n``` c#\r\ninterface I3 : I1\r\n{\r\n    override void M() { Impl3 }\r\n}\r\n```\r\n\r\nbut `C` is not recompiled. What happens when the program is run? An invocation of `(C as I1).M()`\r\n\r\n1. Runs `I1.M`\r\n2. Runs `I2.M`\r\n3. Runs `I3.M`\r\n4. Either 2 or 3, deterministically\r\n5. Throws some kind of runtime exception\r\n\r\n### Permit `partial` in interface?\r\n\r\nGiven that interfaces may be used in ways analogous to the way abstract classes are used, it may be useful to declare them `partial`. This would be particularly useful in the face of generators.\r\n\r\n> ***Proposal:*** Remove the language restriction that interfaces and members of interfaces may not be declared `partial`.\r\n\r\n### `Main` in an interface?\r\n\r\n> ***Open Issue:*** Is a `static Main` method in an interface a candidate to be the program's entry point?\r\n\r\n### Confirm intent to support public non-virtual methods\r\n\r\nCan we please confirm (or reverse) our decision to permit non-virtual public methods in an interface?\r\n\r\n``` c#\r\ninterface IA\r\n{\r\n    public sealed void M() { }\r\n}\r\n```\r\n\r\n> ***Semi-Closed Issue:*** (2017-04-18) We think it is going to be useful, but will come back to it. This is a mental model tripping block.\r\n\r\n### Does an `override` in an interface introduce a new member?\r\n\r\nThere are a few ways to observe whether an override declaration introduces a new member or not.\r\n\r\n``` c#\r\ninterface IA\r\n{\r\n    void M(int x) { }\r\n}\r\ninterface IB\r\n{\r\n    override void M(int y) { }\r\n}\r\ninterface IC\r\n{\r\n    static void M2()\r\n    {\r\n        M(y: 3); // permitted?\r\n    }\r\n    override void IB.M(int z) { } // permitted? What does it override?\r\n}\r\n```\r\n\r\n> ***Open Issue:*** Does an override declaration in an interface introduce a new member?\r\n\r\nIn a class, an overriding method is \"visible\" in some senses. For example, the names of its parameters take precedence over the names of parameters in the overridden method. It may be possible to duplicate that behavior in interfaces, as there is always a most specific override. But do we want to duplicate that behavior?\r\n\r\nAlso, it is possible to \"override\" an override method?\r\n\r\n### Properties with a private accessor\r\n\r\nWe say that private members are not virtual, and the combination of virtual and private is disallowed. But what about a property with a private accessor?\r\n\r\n``` c#\r\ninterface IA\r\n{\r\n    public virtual int P\r\n    {\r\n        get => 3;\r\n        private set => { } \r\n    }\r\n}\r\n```\r\n\r\nIs this allowed? Is the `set` accessor here `virtual` or not? Can it be overridden where it is accessible? Does the following implicitly implement only the `get` accessor?\r\n\r\n``` c#\r\nclass C : IA\r\n{\r\n    public int P\r\n    {\r\n        get => 4;\r\n        set { }\r\n    }\r\n}\r\n```\r\n\r\nIs the following presumably an error because IA.P.set isn't virtual and also because it isn't accessible?\r\n\r\n``` c#\r\nclass C : IA\r\n{\r\n    int IA.P\r\n    {\r\n        get => 4;\r\n        set { }\r\n    }\r\n}\r\n```\r\n\r\n### Base Interface Invocations, round 2\r\n\r\nOur previous \"resolution\" to how to handle base invocations doesn't actually provide sufficient expressiveness. It turns out that in C# and the CLR, unlike Java, you need to specify both the interface containing the method declaration and the location of the implementation you want to invoke.\r\n\r\nI propose the following syntax for base calls in interfaces. I’m not in love with it, but it illustrates what any syntax must be able to express:\r\n\r\n``` c#\r\ninterface I1 { void M(); }\r\ninterface I2 { void M(); }\r\ninterface I3 : I1, I2 { void I1.M() { } void I2.M() { } }\r\ninterface I4 : I1, I2 { void I1.M() { } void I2.M() { } }\r\ninterface I5 : I3, I4\r\n{\r\n    void I1.M()\r\n    {\r\n        base<I3>(I1).M(); // calls I3's implementation of I1.M\r\n        base<I4>(I1).M(); // calls I4's implementation of I1.M\r\n    }\r\n    void I2.M()\r\n    {\r\n        base<I3>(I2).M(); // calls I3's implementation of I2.M\r\n        base<I4>(I2).M(); // calls I4's implementation of I2.M\r\n    }\r\n}\r\n```\r\nIf there is no ambiguity, you can write it more simply\r\n``` c#\r\ninterface I1 { void M(); }\r\ninterface I3 : I1 { void I1.M() { } }\r\ninterface I4 : I1 { void I1.M() { } }\r\ninterface I5 : I3, I4\r\n{\r\n    void I1.M()\r\n    {\r\n        base<I3>.M(); // calls I3's implementation of I1.M\r\n        base<I4>.M(); // calls I4's implementation of I1.M\r\n    }\r\n}\r\n```\r\nOr\r\n``` c#\r\ninterface I1 { void M(); }\r\ninterface I2 { void M(); }\r\ninterface I3 : I1, I2 { void I1.M() { } void I2.M() { } }\r\ninterface I5 : I3\r\n{\r\n    void I1.M()\r\n    {\r\n        base(I1).M(); // calls I3's implementation of I1.M\r\n    }\r\n    void I2.M()\r\n    {\r\n        base(I2).M(); // calls I3's implementation of I2.M\r\n    }\r\n}\r\n```\r\nOr\r\n``` c#\r\ninterface I1 { void M(); }\r\ninterface I3 : I1 { void I1.M() { } }\r\ninterface I5 : I3\r\n{\r\n    void I1.M()\r\n    {\r\n        base.M(); // calls I3's implementation of I1.M\r\n    }\r\n}\r\n```\r\n\r\n### Warning for struct not implementing default method?\r\n\r\n@vancem asserts that we should seriously consider producing a warning if a value type declaration fails to override some interface method, even if it would inherit an implementation of that method from an interface. Because it causes boxing and undermines constrained calls.\r\n\r\n### Interface static constructors\r\n\r\nWhen are interface static constructors run?  The current CLI draft proposes that it occurs when the first static method or field is accessed.  If there are neither of those then it might never be run??\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/csharplang/issues/comments/429309060","html_url":"https://github.com/dotnet/csharplang/issues/406#issuecomment-429309060","issue_url":"https://api.github.com/repos/dotnet/csharplang/issues/406","id":429309060,"node_id":"MDEyOklzc3VlQ29tbWVudDQyOTMwOTA2MA==","user":{"login":"jnm2","id":8040367,"node_id":"MDQ6VXNlcjgwNDAzNjc=","avatar_url":"https://avatars2.githubusercontent.com/u/8040367?v=4","gravatar_id":"","url":"https://api.github.com/users/jnm2","html_url":"https://github.com/jnm2","followers_url":"https://api.github.com/users/jnm2/followers","following_url":"https://api.github.com/users/jnm2/following{/other_user}","gists_url":"https://api.github.com/users/jnm2/gists{/gist_id}","starred_url":"https://api.github.com/users/jnm2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jnm2/subscriptions","organizations_url":"https://api.github.com/users/jnm2/orgs","repos_url":"https://api.github.com/users/jnm2/repos","events_url":"https://api.github.com/users/jnm2/events{/privacy}","received_events_url":"https://api.github.com/users/jnm2/received_events","type":"User","site_admin":false},"created_at":"2018-10-12T12:29:29Z","updated_at":"2018-10-12T12:29:29Z","author_association":"CONTRIBUTOR","body":"@Thaina \r\n\r\n> obj.Increment();\r\n\r\nIsn't the correct syntax actually `((IHasCount)obj).Increment();`? I thought Neil just said that default members don't show up on the implementing type."}}
{"actor":{"display_login":"jnm2"}}