Delen via


standaardinterfacemethoden

Notitie

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Probleem met kampioen: https://github.com/dotnet/csharplang/issues/52

Samenvatting

Voeg ondersteuning toe voor virtuele-extensiemethoden - methoden in interfaces met concrete implementaties. Een klasse of struct die een dergelijke interface implementeert, is vereist om één meest specifieke implementatie te hebben voor de interfacemethode, geïmplementeerd door de klasse of struct, of overgenomen van de basisklassen of interfaces. Met methoden voor virtuele extensies kan een API-auteur methoden toevoegen aan een interface in toekomstige versies zonder de bron- of binaire compatibiliteit met bestaande implementaties van die interface te verbreken.

Deze zijn vergelijkbaar met de 'Standaardmethoden' van Java.

(Op basis van de waarschijnlijke implementatietechniek) vereist deze functie overeenkomstige ondersteuning in de CLI/CLR. Programma's die gebruikmaken van deze functie kunnen niet worden uitgevoerd in eerdere versies van het platform.

Motivatie

De belangrijkste motivaties voor deze functie zijn

  • Met standaardinterfacemethoden kan een API-auteur methoden toevoegen aan een interface in toekomstige versies zonder dat de bron- of binaire compatibiliteit met bestaande implementaties van die interface wordt onderbroken.
  • Met de functie kan C# samenwerken met API's die zijn gericht op Android - en iOS-(Swift), die vergelijkbare functies ondersteunen.
  • Zoals blijkt, biedt het toevoegen van standaardinterface-implementaties de elementen van de taalfunctie 'eigenschappen' (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Eigenschappen zijn gebleken een krachtige programmeertechniek (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf) te zijn.

Gedetailleerd ontwerp

De syntaxis voor een interface wordt uitgebreid om het mogelijk te maken

  • liddeclaraties die constanten, operators, statische constructors en geneste typen declareren;
  • een hoofdtekst voor een methode of indexeerfunctie, eigenschap of gebeurtenistoegangsfunctie (een standaard implementatie);
  • liddeclaraties die statische velden, methoden, eigenschappen, indexeerfuncties en gebeurtenissen declareren;
  • liddeclaraties met behulp van de expliciete interface-implementatiesyntaxis; en
  • Expliciete toegangsaanpassingen (de standaardtoegang is public).

Leden met organen staan de interface toe om een 'standaard'-implementatie te bieden voor de methode in klassen en structs die geen eigen implementatie bieden.

Interfaces bevatten mogelijk geen exemplaarstatus. Hoewel statische velden nu zijn toegestaan, zijn exemplaarvelden niet toegestaan in interfaces. Automatische eigenschappen van instanties worden niet ondersteund in interfaces, omdat ze impliciet een verborgen veld zouden declareren.

Statische en persoonlijke methoden maken nuttige herstructurering en organisatie van code mogelijk die wordt gebruikt voor het implementeren van de openbare API van de interface.

Een methode-override in een interface moet de syntaxis van de expliciete interface-implementatie gebruiken.

Het is een fout om een klassetype, structtype of enumtype te declareren binnen het bereik van een typeparameter die is gedeclareerd met een variance_annotation. De declaratie van C hieronder is bijvoorbeeld een fout.

interface IOuter<out T>
{
    class C { } // error: class declaration within the scope of variant type parameter 'T'
}

Concrete methoden in interfaces

De eenvoudigste vorm van deze functie is de mogelijkheid om een concrete methode in een interface te declareren, een methode met een implementatie.

interface IA
{
    void M() { WriteLine("IA.M"); }
}

Een klasse die deze interface implementeert, hoeft de concrete methode niet te implementeren.

class C : IA { } // OK

IA i = new C();
i.M(); // prints "IA.M"

De definitieve overschrijving voor IA.M in klasse C is de concrete methode M die in IAgedefinieerd is. Houd er rekening mee dat een klasse geen leden overneemt van de interfaces; dat niet wordt gewijzigd door deze functie:

new C().M(); // error: class 'C' does not contain a member 'M'

Binnen een lid van een instantie van een interface heeft this het type van de omsluitende interface.

Modifiers in interfaces

De syntaxis voor een interface is versoepeld om modifiers toe te staan op de leden van de interface. Het volgende is toegestaan: private, protected, internal, public, virtual, abstract, sealed, static, externen partial.

Een interfacelid waarvan de declaratie een hoofdtekst bevat, is een virtual lid, tenzij de sealed of private modifier wordt gebruikt. De virtual modifier kan worden gebruikt voor een functielid dat anders impliciet virtualzou zijn. Hoewel abstract de standaardinstelling is voor interfaceleden zonder lichamen, kan die wijziging expliciet worden gegeven. Een niet-virtueel lid kan worden gedeclareerd met behulp van het trefwoord sealed.

Het is een fout voor een private of sealed functielid van een interface om geen hoofdtekst te hebben. Een private functielid heeft mogelijk niet de wijzigingsfunctie sealed.

Toegangsmodificatoren kunnen worden gebruikt voor toegestane interfaceleden van alle soorten. Het toegangsniveau public is de standaardinstelling, maar deze kan expliciet worden gegeven.

Probleem openen: We moeten de exacte betekenis opgeven van de toegangsaanpassingen, zoals protected en internal, en welke declaraties wel en niet overschrijven (in een afgeleide interface) of ze implementeren (in een klasse die de interface implementeert).

Interfaces kunnen static leden declareren, waaronder geneste typen, methoden, indexeerfuncties, eigenschappen, gebeurtenissen en statische constructors. Het standaardtoegangsniveau voor alle interfaceleden is public.

Interfaces mogen geen instantieconstructors, destructors of velden declareren.

Gesloten probleem: Moeten operatordeclaraties in een interface worden toegestaan? Waarschijnlijk niet conversieoperators, maar hoe zit het met anderen? Beschikking: operators zijn toegestaan behalve voor conversie-, gelijkheids- en ongelijkheidsoperatoren.

Gesloten probleem: Moet new toegestaan worden in declaraties van interfaceleden die leden uit basisinterfaces verbergen? Beslissing: Ja.

Gesloten probleem: We staan momenteel geen partial toe op een interface of de leden ervan. Hiervoor zou een afzonderlijk voorstel nodig zijn. Beslissing: Ja. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

Expliciete implementatie in interfaces

Met expliciete implementaties kan de programmeur een meest specifieke implementatie van een virtueel lid bieden in een interface waar de compiler of runtime er anders geen zou vinden. Een implementatiedeclaratie is toegestaan om expliciet expliciet een bepaalde basisinterfacemethode te implementeren door de declaratie te kwalificeren met de interfacenaam (in dit geval is er geen toegangsaanpassing toegestaan). Impliciete implementaties zijn niet toegestaan.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
    void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}

Expliciete implementaties in interfaces worden mogelijk niet gedeclareerd sealed.

Openbare virtual functieleden in een interface mogen alleen expliciet worden geïmplementeerd in een afgeleide interface (door de naam in de declaratie in te schrijven met het interfacetype dat oorspronkelijk de methode heeft gedeclareerd en een toegangsmodifier weglaat). Het element moet toegankelijk zijn waar het is geïmplementeerd.

Herabstractie

Een virtuele (concrete) methode die in een interface is gedeclareerd, kan opnieuw worden afgetrokken in een afgeleide interface

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.

De abstract modifier is vereist in de verklaring van IB.M, om aan te geven dat IA.M opnieuw wordt afgetrokken.

Dit is handig in afgeleide interfaces waarbij de standaard implementatie van een methode ongepast is en een meer geschikte implementatie moet worden geboden door het implementeren van klassen.

De meest specifieke implementatieregel

We vereisen dat elke interface en klasse een meest specifieke implementatie heeft voor elk virtueel lid onder de implementaties die worden weergegeven in het type of de directe en indirecte interfaces. De meest specifieke implementatie is een unieke implementatie die specifieker is dan elke andere implementatie. Als er geen implementatie is, wordt het lid zelf beschouwd als de meest specifieke implementatie.

Eén implementatie M1 wordt beschouwd als specifiekere dan een andere implementatie M2 als M1 wordt gedeclareerd voor het type T1, M2 wordt gedeclareerd op type T2en een van beide

  1. T1 bevat T2 tussen de directe of indirecte interfaces, of
  2. T2 is een interfacetype, maar T1 is geen interfacetype.

Bijvoorbeeld:

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
    public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'

De meest specifieke implementatieregel zorgt ervoor dat een conflict (d.w. een dubbelzinnigheid die voortvloeit uit diamantovername) expliciet wordt opgelost door de programmeur op het punt waar het conflict zich voordoet.

Omdat we expliciete herabstracties ondersteunen in interfaces, kunnen we dit ook in klassen doen.

abstract class E : IA, IB, IC // ok
{
    abstract void IA.M();
}

gesloten kwestie: moeten we expliciete interface-abstracte implementaties in klassen ondersteunen? Beslissing: NEE

Daarnaast is het een fout als in een klassedeclaratie de meest specifieke implementatie van een interfacemethode een abstracte implementatie is die in een interface is gedeclareerd. Dit is een bestaande regel die is bijgewerkt met behulp van de nieuwe terminologie.

interface IF
{
    void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'

Het is mogelijk dat een virtuele eigenschap die in een interface is gedeclareerd, een meest specifieke implementatie heeft voor de get accessor in één interface en een meest specifieke implementatie voor de set accessor in een andere interface. Dit wordt beschouwd als een schending van de meest specifieke implementatie regel en genereert een compilerfout.

methoden voor static en private

Omdat interfaces nu uitvoerbare code kunnen bevatten, is het handig om algemene code te abstraheren in privé- en statische methoden. We laten deze nu in interfaces toe.

gesloten probleem: Moeten we privémethoden ondersteunen? Moeten we statische methoden ondersteunen? Beslissing: JA

Open probleem: moeten we interfacemethoden toestaan protected of internal of een andere mate van toegang? Zo ja, wat zijn de semantieken? Zijn ze standaard virtual? Zo ja, is er een manier om ze niet-virtueel te maken?

Gesloten probleem: Als we statische methoden ondersteunen, moeten we (statische) operators ondersteunen? Beslissing: JA

Aanroepen van basisinterface

De syntaxis in deze sectie is niet geïmplementeerd. Het blijft een actief voorstel.

Code in een type dat is afgeleid van een interface met een standaardmethode, kan expliciet de 'basis'-implementatie van die interface aanroepen.

interface I0
{
   void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
   override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
   override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
   // an explicit override that invoke's a base interface's default method
   void I0.M() { I2.base.M(); }
}

Een instantiemethode (niet-statisch) kan de implementatie van een toegankelijke instantiemethode in een directe basisinterface niet-virtuaal aanroepen door deze een naam te geven met behulp van de syntaxis base(Type).M. Dit is handig wanneer een overschrijving die vereist is om te bieden vanwege diamanten erfenis wordt opgelost door te delegeren aan één bepaalde basisimplementatie.

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    override void IA.M() { WriteLine("IC.M"); }
}

class D : IA, IB, IC
{
    void IA.M() { base(IB).M(); }
}

Wanneer een virtual of abstract lid wordt benaderd met behulp van de syntaxis base(Type).M, is het vereist dat Type een unieke en meest specifieke overriding voor Mbevat.

Bindende basisclausules

Interfaces bevatten nu typen. Deze typen kunnen worden gebruikt in de basisclausule als basisinterfaces. Bij het binden van een basisclausule moeten we mogelijk de set basisinterfaces kennen om deze typen te binden (bijvoorbeeld om in hen op te zoeken en beveiligde toegang op te lossen). De betekenis van de basiscomponent van een interface wordt dus circulair gedefinieerd. Om de cyclus te doorbreken, voegen we een nieuwe taalregel toe die overeenkomt met een vergelijkbare regel die al is ingesteld voor klassen.

Tijdens het bepalen van de betekenis van de interface_base van een interface, wordt ervan uitgegaan dat de basisinterfaces tijdelijk leeg zijn. Intuïtief zorgt dit ervoor dat de betekenis van een basiscomponent niet recursief afhankelijk is van zichzelf.

We hebben de volgende regels gebruikt:

"Wanneer een klasse B is afgeleid van een klasse A, is het een compilatiefout voor A die afhankelijk is van B. Een klasse rechtstreeks afhankelijk van directe basisklasse (indien aanwezig) en rechtstreeks afhankelijk is van de klasse waarin deze direct is genest (indien aanwezig). Gezien deze definitie is de volledige set klassen waarvan een klasse afhankelijk is, de reflexieve en transitieve sluiting van de rechtstreeks afhankelijk van relatie."

Het is een compileertijdfout als een interface direct of indirect van zichzelf erft. De basisinterfaces van een interface zijn de expliciete basisinterfaces en de bijbehorende basisinterfaces. Met andere woorden, de set van basisinterfaces is de volledige transitieve sluiting van de expliciete basisinterfaces, hun expliciete basisinterfaces, enzovoort.

Wij passen ze als volgt aan:

Wanneer een klasse B is afgeleid van een klasse A, is het een compilatietijdfout voor A om afhankelijk te zijn van B. Een klasse is rechtstreeks afhankelijk van de directe basisklasse (indien aanwezig) en is rechtstreeks afhankelijk van het type waarin deze direct is genest (indien aanwezig).

Wanneer een interface IB een interface IA uitbreidt, is het een compilatiefout als IA afhankelijk is van IB. Een interface is rechtstreeks afhankelijk van zijn directe basisinterfaces (indien van toepassing) en is rechtstreeks afhankelijk van het type waarin deze direct is genest (indien van toepassing).

Gezien deze definities is de volledige set typen waarvan een type afhankelijk is, de reflexieve en transitieve sluiting van de relatie waarbij direct afhankelijk is van.

Effect op bestaande programma's

De hier gepresenteerde regels zijn bedoeld om geen effect te hebben op de betekenis van bestaande programma's.

Voorbeeld 1:

interface IA
{
    void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
    public static void M() { } // method unrelated to 'IA.M' because static
}

Voorbeeld 2:

interface IA
{
    void M();
}
class Base: IA
{
    void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

Dezelfde regels geven vergelijkbare resultaten als de analoge situatie waarbij standaardinterfacemethoden worden gebruikt:

interface IA
{
    void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

gesloten probleem: bevestig dat dit een bedoeld gevolg is van de specificatie. Beslissing: JA

Oplossing van runtimemethode

Gesloten probleem: De specificatie moet het algoritme voor runtimemethodeomzetting beschrijven in het geval van standaardmethoden voor de interface. We moeten ervoor zorgen dat de semantiek consistent is met de taalsemantiek, bijvoorbeeld welke gedeclareerde methoden wel en niet overschrijven of implementeren van een internal methode.

CLR-ondersteunings-API

Om compilers te laten detecteren wanneer ze compileren voor een runtime die deze functie ondersteunt, worden bibliotheken voor dergelijke runtimes gewijzigd om dat feit te adverteren via de API die in https://github.com/dotnet/corefx/issues/17116wordt besproken. We voegen toe

namespace System.Runtime.CompilerServices
{
    public static class RuntimeFeature
    {
        // Presence of the field indicates runtime support
        public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
    }
}

Probleem openen: Is dat de beste naam voor de CLR- functie? De CLR-functie doet veel meer dan alleen dat (bijv. versoepeling van beveiligingsbeperkingen, ondersteunt onderdrukkingen in interfaces, enzovoort). Misschien zou het iets kunnen worden genoemd als "concrete methoden in interfaces", of "eigenschappen"?

Aanvullende gebieden die moeten worden opgegeven

  • [ ] Het zou handig zijn om de soorten bron- en binaire compatibiliteitseffecten te catalogiseren die worden veroorzaakt door het toevoegen van standaardinterfacemethoden en onderdrukkingen aan bestaande interfaces.

Nadelen

Dit voorstel vereist een gecoördineerde update van de CLR-specificatie (ter ondersteuning van concrete methoden in interfaces en methodeoplossing). Het is daarom behoorlijk "duur" en het kan de moeite waard zijn dit te doen in combinatie met andere voorzieningen waarvan we ook verwachten dat ze aanpassingen aan de CLR vereisen.

Alternatieven

Geen.

Niet-opgeloste vragen

  • Open vragen zijn opgenomen in het bovenstaande voorstel.
  • Zie ook https://github.com/dotnet/csharplang/issues/406 voor een lijst met geopende vragen.
  • De gedetailleerde specificatie moet het oplossingsmechanisme beschrijven dat tijdens runtime wordt gebruikt om de exacte methode te selecteren die moet worden aangeroepen.
  • De interactie van metagegevens die worden geproduceerd door nieuwe compilers en die worden gebruikt door oudere compilers, moet uitgebreid worden uitgewerkt. We moeten er bijvoorbeeld voor zorgen dat de metagegevensweergave die we gebruiken, niet leidt tot het toevoegen van een standaard implementatie in een interface om een bestaande klasse te verbreken die die interface implementeert wanneer deze wordt gecompileerd door een oudere compiler. Dit kan van invloed zijn op de metagegevensweergave die we kunnen gebruiken.
  • Het ontwerp moet rekening houden met interoperation met andere talen en bestaande compilers voor andere talen.

Opgeloste vragen

Abstracte Overschrijving

De eerdere ontwerpspecificatie bevatte de mogelijkheid om een overgenomen methode te 'reabstracten'.

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { }
}
interface IC : IB
{
    override void M(); // make it abstract again
}

Uit mijn notities voor 2017-03-20 bleek dat we besloten dit niet toe te staan. Er zijn echter ten minste twee use cases voor:

  1. De Java-API's, waarmee sommige gebruikers van deze functie hopen te interoperabelen, zijn afhankelijk van deze faciliteit.
  2. Programmeren met eigenschappen profiteert van. Reabstraction is een van de elementen van de taalfunctie 'eigenschappen' (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Het volgende is toegestaan met klassen:
public abstract class Base
{
    public abstract void M();
}
public abstract class A : Base
{
    public override void M() { }
}
public abstract class B : A
{
    public override abstract void M(); // reabstract Base.M
}

Helaas kan deze code niet worden geherstructureerd als een set interfaces (eigenschappen) tenzij dit is toegestaan. Volgens het Jared-principe van hebzuchtmoet het worden toegestaan.

Gesloten probleem: Mag herabstraheren worden toegestaan? [JA] Mijn notities waren verkeerd. De LDM-aantekeningen geven aan dat re-abstraction is toegestaan in een interface. Niet in een klas.

Virtuele modifier versus verzegelde modifier

Van Aleksey Tsingauz:

We hebben besloten om modifiers expliciet op interfaceleden toe te staan, tenzij er een reden is om sommige van hen niet toe te staan. Dit brengt een interessante vraag over virtuele modifier. Moet dit vereist zijn voor leden met standaard implementatie?

Dat kunnen we zeggen:

  • als er geen implementatie is opgegeven en noch virtueel noch verzegeld is, gaan we ervan uit dat het lid abstract is.
  • als er een implementatie is en noch abstract, noch sealed zijn opgegeven, nemen we aan dat het lid virtueel is.
  • De sealed-modifier is vereist om een methode niet virtueel en niet abstract te maken.

U kunt ook zeggen dat virtuele modifier vereist is voor een virtueel lid. Als er dus een lid is met implementatie die niet expliciet is gemarkeerd met virtuele modifier, is het noch virtueel, noch abstract. Deze benadering kan een betere ervaring bieden wanneer een methode wordt verplaatst van een klasse naar een interface:

  • een abstracte methode blijft abstract.
  • een virtuele methode blijft virtueel.
  • een methode zonder enige wijziging blijft noch virtueel, noch abstract.
  • Een sealed modifier kan niet worden toegepast op een methode die geen override is.

Wat denk je?

Afgesloten probleem: Moet een concrete methode (met implementatie) impliciet zijn virtual? [JA]

Beslissingen: Gemaakt in LDM 2017-04-05

  1. niet-virtueel moet expliciet worden uitgedrukt door middel van sealed of private.
  2. sealed is het trefwoord om leden van interface-exemplaren met implementatie niet-virtueel te maken
  3. We willen alle modifiers in interfaces toestaan
  4. Standaardtoegankelijkheid voor interfaceleden is openbaar, inclusief geneste typen
  5. Privéfunctieleden in interfaces worden impliciet verzegeld en sealed is daarop niet toegestaan.
  6. Privéklassen (in interfaces) zijn toegestaan en kunnen worden verzegeld, en dat betekent verzegeld in de klasse-zin van verzegeld.
  7. Bij afwezigheid van een goed voorstel is gedeeltelijk nog steeds niet toegestaan op interfaces of hun leden.

Binaire compatibiliteit 1

Wanneer een bibliotheek een standaard implementatie biedt

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}

We begrijpen dat de implementatie van I1.M in CI1.Mis. Wat als de assembly met I2 als volgt wordt gewijzigd en opnieuw wordt gecompileerd

interface I2 : I1
{
    override void M() { Impl2 }
}

maar C is niet opnieuw gecompileerd. Wat gebeurt er wanneer het programma wordt uitgevoerd? Een aanroep van (C as I1).M()

  1. Voert uit I1.M
  2. Voert uit I2.M
  3. Genereert een soort runtime-fout

Beslissing: Gemaakt op 2017-04-11: Voert I2.Muit, wat de ondubbelzinnig meest specifieke overschrijving is tijdens runtime.

Gebeurtenistoegangsmethoden (gesloten)

Gesloten probleem: Kan een gebeurtenis stapsgewijs worden overschreven?

Houd rekening met dit geval:

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        // error: "remove" accessor missing
    }
}

Deze 'gedeeltelijke' implementatie van de gebeurtenis is niet toegestaan omdat, zoals in een klasse het geval is, de syntaxis voor een gebeurtenisdeclaratie niet slechts één toegangsmethode toestaat; beide (of geen) moeten worden verstrekt. U kunt hetzelfde doen door toe te staan dat de abstracte verwijdertoegangsfunctie in de syntaxis impliciet abstract wordt door afwezigheid van een hoofdtekst:

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        remove; // implicitly abstract
    }
}

Houd er rekening mee dat dit een nieuwe (voorgestelde) syntaxis is. In de huidige grammatica hebben gebeurtenistoegangsfuncties een verplicht lichaam.

Gesloten probleem: Kan een gebeurtenistoegangsfunctie (impliciet) abstract zijn door het weglaten van een lichaam, vergelijkbaar met de manier waarop methoden in interfaces en eigenschapstoegangsfuncties (impliciet) abstract zijn door het weglaten van een lichaam?

Beschikking: (2017-04-18) Nee, gebeurtenisdeclaraties vereisen zowel concrete toegangsrechten (of geen van beide).

Herabstractie in een klasse (gesloten)

Gesloten probleem: We moeten bevestigen dat dit is toegestaan (anders is het toevoegen van een standaard implementatie een belangrijke wijziging):

interface I1
{
    void M() { }
}
abstract class C : I1
{
    public abstract void M(); // implement I1.M with an abstract method in C
}

Beslissing: (2017-04-18) Ja, het toevoegen van een hoofdtekst aan een declaratie van een interfacelid mag C niet breken.

Afgesloten overbrugging (gesloten)

Bij de vorige vraag wordt impliciet ervan uitgegaan dat de sealed wijzigingsfunctie kan worden toegepast op een override in een interface. Dit is in strijd met de ontwerpspecificatie. Willen we toestaan dat een override vergrendeld wordt? Bron- en binaire compatibiliteitseffecten van verzegeling moeten worden overwogen.

Gesloten probleem: Moeten we het sealen van een override toestaan?

Beslissing: (2017-04-18) Laten we geen sealed toestaan bij overschrijvingen in interfaces. Het enige gebruik van sealed op interface-leden is om ze niet-virtueel te maken in hun eerste declaratie.

Diamanterfenis en klassen (gesloten)

Het concept van het voorstel geeft de voorkeur aan klasse-onderdrukkingen voor interface-onderdrukkingen in scenario's voor diamantovername:

We vereisen dat elke interface en klasse een meest specifieke overschrijving heeft voor elke interfacemethode die voorkomt onder de overschrijvingen die worden weergegeven in het type of de directe en indirecte interfaces. Het meest specifieke overschrijving is een uniek overschrijving dat specifieker is dan elk ander overschrijving. Als er geen overschrijving is, wordt de methode zelf beschouwd als de specifiekste overschrijving.

Een overschrijving M1 wordt beschouwd als specifiekere dan een andere overschrijving M2 als M1 wordt gedeclareerd voor het type T1, M2 wordt gedeclareerd voor het type T2en een van beide

  1. T1 bevat T2 tussen de directe of indirecte interfaces, of
  2. T2 is een interfacetype, maar T1 is geen interfacetype.

Het scenario is dit

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { WriteLine("IB"); }
}
class Base : IA
{
    void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
    static void Main()
    {
        IA a = new Derived();
        a.M();           // what does it do?
    }
}

We moeten dit gedrag bevestigen (of anders beslissen)

gesloten probleem: bevestig de bovenstaande conceptspecificatie voor meest specifieke onderdrukking zoals deze van toepassing is op gemengde klassen en interfaces (een klasse heeft prioriteit boven een interface). Zie https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes.

Interfacemethoden versus structs (gesloten)

Er zijn enkele ongelukkige interacties tussen standaardinterfacemethoden en structs.

interface IA
{
    public void M() { }
}
struct S : IA
{
}

Houd er rekening mee dat interfaceleden niet worden overgenomen:

var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'

Als gevolg hiervan moet de client de struct verpakken om interfacemethoden aan te roepen.

IA s = default(S); // an S, boxed
s.M(); // ok

Boksen op deze manier doet de belangrijkste voordelen van een struct type teniet. Bovendien zullen alle mutatiemethoden geen duidelijk effect hebben, omdat ze werken op een geboksde kopie van de struct:

interface IB
{
    public void Increment() { P += 1; }
    public int P { get; set; }
}
struct T : IB
{
    public int P { get; set; } // auto-property
}

T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0

gesloten probleem: Wat kunnen we doen:

  1. Verbied een struct van het overnemen van een standaard implementatie. Alle methoden van de interface worden in een structals abstract behandeld. Dan kunnen we later tijd nemen om te bepalen hoe het beter werkt.
  2. Bedenk een soort strategie voor het genereren van code die boksen vermijdt. Binnen een methode zoals IB.Incrementzou het type this mogelijk vergelijkbaar zijn met een typeparameter die is beperkt tot IB. In combinatie daarmee, om boksen in de aanroeper te voorkomen, worden niet-abstracte methoden overgenomen van interfaces. Dit kan de inspanning voor de compiler- en CLR-implementatie aanzienlijk verhogen.
  3. Maak je er geen zorgen over en laat het gewoon als een wrat achter.
  4. Andere ideeën?

Beslissing: Maak je geen zorgen en laat het gewoon als een wrat. Zie https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations.

Aanroepen van de basisinterface (gesloten)

Deze beslissing is niet geïmplementeerd in C# 8. De base(Interface).M()-syntaxis is niet geïmplementeerd.

In de conceptspecificatie wordt een syntaxis voorgesteld voor aanroepen van de basisinterface die zijn geïnspireerd op Java: Interface.base.M(). We moeten een syntaxis selecteren, ten minste voor het eerste prototype. Mijn favoriet is base<Interface>.M().

Gesloten probleem: Wat is de syntaxis voor een aanroep van een basislid?

Beslissing: De syntaxis is base(Interface).M(). Zie https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. De interface met de naam moet een basisinterface zijn, maar hoeft geen directe basisinterface te zijn.

Open kwestie: Moeten aanroepen van de basisinterface in leden van de klasse zijn toegestaan?

Beslissing: Ja. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation

Niet-openbare interfaceleden overschrijven (gesloten)

In een interface worden niet-openbare leden van basisinterfaces overschreven met behulp van de override-modificator. Als het een 'expliciete' override is die de interface met het lid een naam geeft, wordt de toegangsmodificator weggelaten.

Afgesloten kwestie: Als het een "impliciete" overschrijving is die de interface geen naam geeft, moet de toegangsmodifier overeenkomen?

Beslissing: Alleen openbare leden kunnen impliciet worden overschreven en de toegang moet overeenkomen. Zie https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Openstaand probleem: Is de toegangsmodificator vereist, optioneel of weggelaten bij een expliciete override zoals override void IB.M() {}?

Openstaande probleem: is override vereist, optioneel of weggelaten bij een expliciete overschrijving zoals void IB.M() {}?

Hoe implementeert u een niet-openbaar interfacelid in een klasse? Misschien moet dit expliciet worden gedaan?

interface IA
{
    internal void MI();
    protected void MP();
}
class C : IA
{
    // are these implementations?  Decision: NO
    internal void MI() {}
    protected void MP() {}
}

Gesloten probleem: Hoe implementeert u een lid van een niet-openbare interface in een klasse?

Beslissing: U kunt alleen niet-openbare interfaceleden expliciet implementeren. Zie https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Decision: Geen override trefwoord toegestaan voor interface-leden. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

Binaire compatibiliteit 2 (gesloten)

Houd rekening met de volgende code waarin elk type zich in een afzonderlijke assembly bevindt

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
    override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}

We begrijpen dat de implementatie van I1.M in CI2.Mis. Wat als de assembly met I3 als volgt wordt gewijzigd en opnieuw wordt gecompileerd

interface I3 : I1
{
    override void M() { Impl3 }
}

maar C is niet opnieuw gecompileerd. Wat gebeurt er wanneer het programma wordt uitgevoerd? Een aanroep van (C as I1).M()

  1. Voert uit I1.M
  2. Voert uit I2.M
  3. Voert uit I3.M
  4. Hetzij 2 of 3, op deterministische wijze
  5. Genereert een soort runtime-uitzondering

Besluit: Een uitzondering genereren (5). Zie https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods.

partial in interface toestaan? (gesloten)

Aangezien interfaces kunnen worden gebruikt op manieren die vergelijkbaar zijn met de manier waarop abstracte klassen worden gebruikt, kan het nuttig zijn om ze te declareren partial. Dit zou vooral nuttig zijn bij het gebruik van generatoren.

Voorstel: de taalbeperking verwijderen die interfaces en leden van interfaces mogelijk niet partialworden gedeclareerd.

Beslissing: Ja. Zie https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface.

Main in een interface? (gesloten)

Probleem geopend: Is een static Main-methode in een interface een kandidaat voor het toegangspunt van het programma?

Beslissing: Ja. Zie https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface.

Intentie bevestigen ter ondersteuning van openbare niet-virtuele methoden (gesloten)

Kunnen we bevestigen (of omkeren) onze beslissing om niet-virtuele openbare methoden in een interface toe te laten?

interface IA
{
    public sealed void M() { }
}

Semi-Closed Probleem: (2017-04-18) We denken dat het nuttig zal zijn, maar we zullen erop terugkomen. Dit is een struikelblok in het mentale model.

Beslissing: Ja. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods.

Introduceert een override in een interface een nieuw lid? (gesloten)

Er zijn enkele manieren om te zien of een overschrijvingsdeclaratie een nieuw lid introduceert of niet.

interface IA
{
    void M(int x) { }
}
interface IB : IA
{
    override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
    static void M2()
    {
        M(y: 3); // permitted? Decision: No.
    }
    override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}

Openstaand issue: Introduceert een override-declaratie in een interface een nieuw lid? (gesloten)

In een klasse is een override-methode in sommige opzichten "zichtbaar". De namen van de parameters hebben bijvoorbeeld voorrang boven de namen van parameters in de methode die is overschreven. Het is mogelijk dat dat gedrag in interfaces kan worden gedupliceerd, omdat er altijd een meest specifieke override is. Maar willen we dat gedrag dupliceren?

Is het ook mogelijk om een override-methode te 'overschrijven'? Onbelangrijk / Ter discussie

Decision: Geen override trefwoord toegestaan voor interface-leden. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.

Eigenschappen met een privétoegangsmethode (afgesloten)

We zeggen dat privéleden niet virtueel zijn en dat de combinatie van virtueel en privé niet is toegestaan. Maar hoe zit het met een woning met een privétoegangsor?

interface IA
{
    public virtual int P
    {
        get => 3;
        private set { }
    }
}

Is dit toegestaan? Is de set accessor hier virtual of is dat niet zo? Kan het worden overschreven waar het toegankelijk is? Implementeert het volgende impliciet alleen de get accessor?

class C : IA
{
    public int P
    {
        get => 4;
        set { }
    }
}

Is het volgende waarschijnlijk een fout omdat IA.P.set niet virtueel is en ook omdat deze niet toegankelijk is?

class C : IA
{
    int IA.P
    {
        get => 4;
        set { } // Decision: Not valid
    }
}

Beslissing: het eerste voorbeeld ziet er geldig uit, terwijl het laatste niet het geval is. Dit wordt opgelost op een vergelijkbare manier als hoe het al werkt in C#. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor

Basisinterface-aanroepen, ronde 2 (gesloten)

Dit is niet geïmplementeerd in C# 8.

Onze vorige "resolutie" voor het afhandelen van basisoproepen biedt niet voldoende mogelijkheden voor expressie. Het blijkt dat u in C# en de CLR, in tegenstelling tot Java, zowel de interface met de methodedeclaratie als de locatie van de implementatie moet opgeven die u wilt aanroepen.

Ik stel de volgende syntaxis voor basisoproepen in interfaces voor. Ik ben er niet dol op, maar het illustreert wat elke syntaxis moet kunnen uitdrukken:

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>(I1).M(); // calls I3's implementation of I1.M
        base<I4>(I1).M(); // calls I4's implementation of I1.M
    }
    void I2.M()
    {
        base<I3>(I2).M(); // calls I3's implementation of I2.M
        base<I4>(I2).M(); // calls I4's implementation of I2.M
    }
}

Als er geen dubbelzinnigheid is, kunt u deze eenvoudiger schrijven

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>.M(); // calls I3's implementation of I1.M
        base<I4>.M(); // calls I4's implementation of I1.M
    }
}

Of

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base(I1).M(); // calls I3's implementation of I1.M
    }
    void I2.M()
    {
        base(I2).M(); // calls I3's implementation of I2.M
    }
}

Of

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base.M(); // calls I3's implementation of I1.M
    }
}

Besluit: besloten op base(N.I1<T>).M(s), waarbij wordt erkend dat er, indien wij een oproepbinding hebben, later mogelijk een probleem kan ontstaan. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations

Waarschuwing voor het niet implementeren van standaardmethode voor struct? (gesloten)

@vancem stelt dat we serieus moeten overwegen om een waarschuwing te maken als een declaratie van een waardetype een bepaalde interfacemethode overschrijft, zelfs als deze een implementatie van die methode van een interface zou overnemen. Omdat het boxing veroorzaakt en beperkte oproepen ondermijnt.

Decision: dit lijkt iets geschikter voor een analyse. Het lijkt er ook op dat deze waarschuwing luidruchtig kan zijn, omdat deze wordt geactiveerd, zelfs als de standaardinterfacemethode nooit wordt aangeroepen en er nooit boksen zal plaatsvinden. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method

Statische interfaceconstructors (gesloten)

Wanneer worden statische constructors van de interface uitgevoerd? Het huidige CLI-concept stelt voor dat dit gebeurt wanneer de eerste statische methode of het eerste statische veld wordt geopend. Als er geen van die zijn, kan het nooit worden uitgevoerd?

[2018-10-09 Het CLR-team stelt voor om hetzelfde te doen als wat we doen voor waardetypen (cctor check on toegang tot elke instantiemethode)]

Decision: Statische constructors worden ook uitgevoerd bij het invoeren van exemplaarmethoden, als de statische constructor niet is beforefieldinit, in welk geval statische constructors worden uitgevoerd voordat ze toegang krijgen tot het eerste statische veld. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run

Ontwerpvergaderingen

2017-03-08 LDM-vergadernotities2017-03-21 LDM-vergadernotities2017-03-23 vergadering "CLR Gedrag voor Standaardinterfacemethoden"2017-04-05 LDM-vergadernotities2017-04-11 LDM-vergadernotities2017-04-18 LDM-vergadernotities2017-04-19 LDM-vergadernotities2017-05-17 LDM-vergadernotities2017-05-31 LDM-vergadernotities2017-06-14 LDM-vergadernotities2018-10-17 LDM-vergadernotities2018-11-14 LDM-vergadernotities