Dela via


standardgränssnittsmetoder

Not

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningar (Language Design Meeting).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Sammanfattning

Lägg till stöd för virtuella tilläggsmetoder – metoder i gränssnitt med konkreta implementeringar. En klass eller struct som implementerar ett sådant gränssnitt måste ha en enda mest specifik implementering för en metod i gränssnittet, antingen implementerad av klassen eller structen, eller ärvt från dess basklasser eller basgränssnitt. Med metoder för virtuella tillägg kan en API-författare lägga till metoder i ett gränssnitt i framtida versioner utan att bryta käll- eller binärkompatibilitet med befintliga implementeringar av gränssnittet.

Dessa liknar Javas "Standardmetoder".

(Baserat på den troliga implementeringstekniken) kräver den här funktionen motsvarande stöd i CLI/CLR. Program som drar nytta av den här funktionen kan inte köras på tidigare versioner av plattformen.

Motivation

Huvudmotiven för den här funktionen är

Detaljerad design

Syntaxen för ett gränssnitt utökas för att tillåta

  • medlemsdeklarationer som deklarerar konstanter, operatorer, statiska konstruktorer och kapslade typer.
  • en brödtext för en metod eller indexerare, egenskap eller händelseåtkomstor (dvs. en "standardimplementering").
  • medlemsdeklarationer som deklarerar statiska fält, metoder, egenskaper, indexerare och händelser;
  • medlemsdeklarationer med hjälp av den explicita syntaxen för gränssnittsimplementering. och
  • Explicita åtkomstmodifierare (standardåtkomsten är public).

Medlemmar med organ tillåter gränssnittet att tillhandahålla en "standardimplementering" för metoden i klasser och structs som inte tillhandahåller sin egen implementering.

Gränssnitt får inte innehålla instanstillstånd. Statiska fält tillåts nu, men instansfält tillåts inte i gränssnitt. Autoegenskaper för instanser stöds inte i gränssnitt, eftersom de implicit deklarerar ett dolt fält.

Med statiska och privata metoder kan du omstrukturera och organisera kod som används för att implementera gränssnittets offentliga API.

En metodöverskrivning i ett gränssnitt måste använda explicit syntax för gränssnittsimplementering.

Det är ett fel att deklarera en klasstyp, structtyp eller enumtyp inom en typparameters omfång som deklarerades med en variance_annotation. Deklarationen av C nedan är till exempel ett fel.

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

Konkreta metoder i gränssnitt

Den enklaste formen av den här funktionen är möjligheten att deklarera en konkret metod i ett gränssnitt, vilket är en metod med en brödtext.

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

En klass som implementerar det här gränssnittet behöver inte implementera sin konkreta metod.

class C : IA { } // OK

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

Den sista åsidosättningen för IA.M i klass C är den konkreta metoden M som deklareras i IA. Observera att en klass inte ärver medlemmar från dess gränssnitt. som inte ändras av den här funktionen:

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

Inom en instansmedlem i ett gränssnitt har this typen av det omslutande gränssnittet.

Modifierare i gränssnitt

Syntaxen för ett gränssnitt har gjorts mer flexibel för att tillåta modifierare för dess medlemmar. Följande är tillåtna: private, protected, internal, public, virtual, abstract, sealed, static, externoch partial.

En gränssnittsmedlem vars deklaration innehåller en brödtext är en virtual medlem såvida inte sealed eller private modifieraren används. Den virtual modifieraren kan användas på en funktionsmedlem som annars implicit skulle virtual. Även om abstract är standard för gränssnittsmedlemmar utan organ, kan den modifieraren ges uttryckligen. En icke-virtuell medlem kan deklareras med nyckelordet sealed.

Det är ett fel om en private- eller sealed-funktionsmedlem i ett gränssnitt saknar en kropp. En private funktionsmedlem kanske inte har modifieraren sealed.

Åtkomstmodifierare kan användas på alla tillåtna typer av medlemmar i ett gränssnitt. Åtkomstnivån public är standard men den kan anges explicit.

Öppet problem: Vi måste ange den exakta innebörden av åtkomstmodifierarna, till exempel protected och internal, och vilka deklarationer som åsidosätter dem (i ett härlett gränssnitt) eller implementerar dem (i en klass som implementerar gränssnittet).

Gränssnitt kan deklarera static medlemmar, inklusive kapslade typer, metoder, indexerare, egenskaper, händelser och statiska konstruktorer. Standardåtkomstnivån för alla gränssnittsmedlemmar är public.

Gränssnitt får inte deklarera instanskonstruktorer, destruktörer eller fält.

Stängt problem: Ska operatordeklarationer tillåtas i ett gränssnitt? Förmodligen inte konverteringsoperatorer, men hur är det med andra? Beslut: Operatörer tillåts förutom för konverterings-, likhets- och ojämlikhetsoperatorer.

Stängt problem: Ska new tillåtas för medlemsdeklarationer i gränssnittet som döljer medlemmar från basgränssnitt? Beslut: Ja.

Stängt problem: Vi tillåter för närvarande inte partial i ett gränssnitt eller dess medlemmar. Det skulle kräva ett separat förslag. Beslut: Ja. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

Explicit implementering i gränssnitt

Med explicita implementeringar kan programmeraren tillhandahålla en så specifik som möjligt implementering av en virtuell medlem i ett gränssnitt där kompilatorn eller körmiljön annars inte skulle hitta någon. En implementeringsdeklaration tillåts uttryckligen implementera en viss basgränssnittsmetod genom att kvalificera deklarationen med gränssnittsnamnet (ingen åtkomstmodifierare tillåts i det här fallet). Implicita implementeringar är inte tillåtna.

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
}

Explicita implementeringar i gränssnitt får inte deklareras sealed.

Offentliga virtual funktionsmedlemmar i ett gränssnitt får endast implementeras i ett härlett gränssnitt explicit (genom att kvalificera namnet i deklarationen med den gränssnittstyp som ursprungligen deklarerade metoden och utelämna en åtkomstmodifierare). Medlemmen måste vara tillgänglig där den implementeras.

Reabstraction

En konkret (virtuell) metod som deklareras i ett gränssnitt kan omabstraheras i ett härlett gränssnitt.

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'.

Den abstract-modifieraren krävs i IB.Ms deklaration för att indikera att IA.M rekonstrueras.

Detta är användbart i härledda gränssnitt där standardimplementeringen av en metod är olämplig och en lämpligare implementering bör tillhandahållas av implementeringsklasser.

Den mest specifika implementeringsregeln

Vi kräver att varje gränssnitt och klass har en mest specifika implementering för varje virtuell medlem bland de implementeringar som visas i typen eller dess direkta och indirekta gränssnitt. Den mest specifika implementeringen är en unik implementering som är mer specifik än alla andra implementeringar. Om det inte finns någon implementering, betraktas medlemmen själv som den mest specifika implementeringen.

En implementering M1 anses mer specifik än en annan implementering M2 om M1 deklareras på typen T1, M2 deklareras på typen T2och antingen

  1. T1 innehåller T2 bland dess direkta eller indirekta gränssnitt, eller
  2. T2 är en gränssnittstyp men T1 är inte en gränssnittstyp.

Till exempel:

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'

Den mest specifika implementeringsregeln säkerställer att en konflikt (dvs. en tvetydighet som uppstår till följd av diamantarv) uttryckligen löses av programmeraren vid den tidpunkt då konflikten uppstår.

Eftersom vi stöder explicita omabstraktioner i gränssnitt kan vi även göra det i klasser

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

Stängt problem: ska vi stödja explicita gränssnittsabstranta implementeringar i klasser? Beslut: NEJ

Dessutom är det ett fel om den mest specifika implementeringen av någon gränssnittsmetod i en klassdeklaration är en abstrakt implementering som deklarerades i ett gränssnitt. Det här är en befintlig regel som har omräknats med den nya terminologin.

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

Det är möjligt att en virtuell egenskap som deklarerats i ett gränssnitt har en mest specifik implementering för sin get-accessor i ett gränssnitt och en mest specifik implementering för dess set-accessor i ett annat gränssnitt. Detta anses vara ett brott mot den mest specifika implementeringsregeln och genererar ett kompilatorfel.

static och private metoder

Eftersom gränssnitt nu kan innehålla körbar kod är det användbart att abstrahera vanlig kod till privata och statiska metoder. Nu tillåter vi dessa i gränssnitt.

Stängt problem: Ska vi stödja privata metoder? Ska vi stödja statiska metoder? Beslut: JA

Öppet ärende: ska vi tillåta att gränssnittsmetoder har protected eller internal eller annan åtkomst? I så fall, vad är semantiken? Är de virtual som standard? Finns det i så fall ett sätt att göra dem icke-virtuella?

Stängt problem: Om vi stöder statiska metoder bör vi ha stöd för (statiska) operatorer? Beslut: JA

Anrop till basgränssnitt

Syntaxen i det här avsnittet har inte implementerats. Det är fortfarande ett aktivt förslag.

Kod i en typ som härleds från ett gränssnitt med en standardmetod kan uttryckligen anropa gränssnittets "bas"-implementering.

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(); }
}

En instansmetod (icke-statisk) tillåts anropa implementeringen av en tillgänglig instansmetod i ett direkt basgränssnitt ickevirtualt genom att namnge den med hjälp av syntaxen base(Type).M. Detta är användbart när en åsidosättning som måste tillhandahållas på grund av diamantarv löses genom att delegera till en specifik basimplementation.

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(); }
}

När en virtual- eller abstract-medlem används med hjälp av syntaxen base(Type).Mmåste Type innehålla en unik mest specifika-åsidosättning för M.

Bindningsbassatser

Gränssnitten innehåller nu typer. Dessa typer kan användas i bassatsen som basgränssnitt. När vi binder en bassats kan vi behöva känna till uppsättningen med basgränssnitt för att binda dessa typer (t.ex. för att söka i dem och för att lösa skyddad åtkomst). Innebörden av ett gränssnitts bassats definieras därför cirkulärt. För att bryta cykeln lägger vi till nya språkregler som motsvarar en liknande regel som redan finns för klasser.

När du fastställer innebörden av interface_base i ett gränssnitt antas basgränssnitten tillfälligt vara tomma. Intuitivt säkerställer detta att innebörden av en bassats inte rekursivt kan vara beroende av sig själv.

Vi brukade ha följande regler:

"När en klass B härleds från en klass A är det ett kompileringsfel för A att vara beroende av B. En klass beror direkt på dess direkta basklass (om någon) och är direkt beroende avklass där den omedelbart kapslas (om någon). Enligt denna definition är den kompletta uppsättningen klasser som en klass beror på den reflexiva och transitiva stängningen av direkt beroende av relation."

Det är ett kompileringsfel om ett gränssnitt direkt eller indirekt ärver från sig självt. De basgränssnitten för ett gränssnitt är de explicita basgränssnitten och deras basgränssnitt. Med andra ord är uppsättningen med basgränssnitt den fullständiga transitiva avslutningen av de explicita basgränssnitten, deras explicita basgränssnitt och så vidare.

Vi justerar dem på följande sätt:

När en klass B härleds från en klass A är det ett kompileringsfel för A att vara beroende av B. En klass är direkt beroende av dess direkta basklass (om någon) och är direkt beroende avtyp där den omedelbart kapslas (om någon).

När ett gränssnitts-IB utökar ett gränssnitts-IA är det ett kompileringsfel för IA att vara beroende av IB. Ett gränssnitt är direkt beroende av dess direkta basgränssnitt (om några) och är direkt beroende av den typ inom vilken det omedelbart kapslas (om någon).

Med dessa definitioner är den fullständiga uppsättningen typer som en typ är beroende av är den reflexiva och transitiva stängningen av direkt beroende av relation.

Effekt på befintliga program

De regler som presenteras här är avsedda att inte ha någon effekt på innebörden av befintliga program.

Exempel 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
}

Exempel 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
}

Samma regler ger liknande resultat som den analoga situationen med standardgränssnittsmetoder:

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
}

Stängt problem: bekräfta att detta är en avsedd konsekvens av specifikationen. Beslut: JA

Körningstidsmetodupplösning

Stängd fråga: Specifikationen bör beskriva algoritmen för metodupplösning under körning i närvaro av standardmetoder för gränssnitt. Vi måste se till att semantiken överensstämmer med språksemantiken, t.ex. vilka deklarerade metoder som åsidosätter eller implementerar en internal-metod, och vilka som inte gör det.

CLR-stöd-API

För att kompilatorer ska kunna identifiera när de kompilerar för en körning som stöder den här funktionen, ändras bibliotek för sådana körningar för att annonsera detta genom API:et som beskrivs i https://github.com/dotnet/corefx/issues/17116. Vi lägger till

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

Öppet ärende: Är det det bästa namnet för funktionen CLR? CLR-funktionen gör mycket mer än bara det (t.ex. minskar skyddsbegränsningarna, stöder åsidosättningar i gränssnitt osv.). Kanske bör det kallas något som "konkreta metoder i gränssnitt" eller "egenskaper"?

Ytterligare områden som ska anges

  • [ ] Det skulle vara användbart att katalogisera de typer av käll- och binärkompatibilitetseffekter som orsakas av att lägga till standardgränssnittsmetoder och åsidosättningar i befintliga gränssnitt.

Nackdelar

Detta förslag kräver en samordnad uppdatering av CLR-specifikationen (för att stödja konkreta metoder i gränssnitt och metodmatchning). Det är därför ganska "dyrt" och det kan vara värt att göra i kombination med andra funktioner som vi också förväntar oss skulle kräva CLR-ändringar.

Alternativ

Ingen.

Olösta frågor

  • Öppna frågor tas upp i hela förslaget ovan.
  • Se även https://github.com/dotnet/csharplang/issues/406 för en lista med öppna frågor.
  • Den detaljerade specifikationen måste beskriva den lösningsmekanism som används vid körning för att välja den exakta metod som ska anropas.
  • Interaktionen mellan metadata som skapas av nya kompilatorer och som används av äldre kompilatorer måste utarbetas i detalj. Vi måste till exempel se till att metadatarepresentationen som vi använder inte gör att tillägget av en standardimplementering i ett gränssnitt bryter en befintlig klass som implementerar gränssnittet när det kompileras av en äldre kompilator. Detta kan påverka den metadatarepresentation som vi kan använda.
  • Designen måste överväga att samverka med andra språk och befintliga kompilatorer för andra språk.

Lösta frågor

Abstrakt åsidosättning

Det tidigare utkastet innehöll möjligheten att återskapa en ärvd metod:

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

Mina anteckningar för 2017-03-20 visade att vi bestämde oss för att inte tillåta detta. Det finns dock minst två användningsfall för det:

  1. Java-API:erna, som vissa användare av den här funktionen hoppas kunna samverka med, är beroende av den här funktionen.
  2. Programmering som använder egenskaper drar nytta av detta. Reabstraction är ett av elementen i "drag"-språkfunktionen (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Följande är tillåtet med klasser:
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
}

Tyvärr kan den här koden inte omstruktureras som en uppsättning gränssnitt (egenskaper) om inte detta är tillåtet. Enligt Jareds princip om girighetbör det tillåtas.

Stängt problem: Ska reabstraction tillåtas? [JA] Mina anteckningar var felaktiga. LDM-anteckningar säger att reabstraction tillåts i ett gränssnitt. Inte i en kurs.

Virtuell modifierare jämfört med förseglad modifierare

Från Aleksey Tsingauz:

Vi beslutade att tillåta att modifierare uttryckligen anges för gränssnittsmedlemmar, såvida det inte finns anledning att inte tillåta vissa av dem. Detta ger en intressant fråga kring virtuell modifierare. Ska det krävas för medlemmar med standardimplementering?

Vi skulle kunna säga att:

  • Om det inte finns någon implementering och varken virtuell eller förseglad anges, antar vi att medlemsfunktionen är abstrakt.
  • Om det finns en implementering och varken abstrakt eller förseglad anges antar vi att medlemmen är virtuell.
  • Sealed-modifierare krävs för att en metod ska vara varken virtuell eller abstrakt.

Alternativt kan vi säga att en virtuell modifier behövs för en virtuell medlem. Om det finns en medlem med en implementering som inte uttryckligen är markerad med den virtuella modifieraren, är den varken virtuell eller abstrakt. Den här metoden kan ge bättre upplevelse när en metod flyttas från en klass till ett gränssnitt:

  • en abstrakt metod förblir abstrakt.
  • en virtuell metod förblir virtuell.
  • en metod utan någon modifierare förblir varken virtuell eller abstrakt.
  • förseglad modifierare kan inte tillämpas på en metod som inte är en åsidosättning.

Vad tror du?

Avslutat ärende: Ska en konkret metod (med implementering) implicit virtual? [JA]

beslut: som fattades i LDM 2017-04-05:

  1. icke-virtuell bör uttryckligen uttryckas via sealed eller private.
  2. sealed är nyckelordet för att göra instansmedlemmar i gränssnitt med kroppar icke-virtuella
  3. Vi vill tillåta alla modifierare i gränssnitt
  4. Standardtillgänglighet för gränssnittsmedlemmar är offentlig, inklusive kapslade typer
  5. privata funktionsmedlemmar i gränssnitt är implicit förseglade och sealed tillåts inte på dem.
  6. Privata klasser (i interface) är tillåtna och kan förseglas, och det innebär att de är förseglade i meningen som förseglade klasser.
  7. Utan ett bra förslag är det fortfarande inte tillåtet med delvis stöd för gränssnitt eller deras medlemmar.

Binär kompatibilitet 1

När ett bibliotek tillhandahåller en standardimplementering

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

Vi förstår att implementeringen av I1.M i C är I1.M. Vad händer om sammansättningen som innehåller I2 ändras på följande sätt och omkompileras

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

men C är inte omkompilerad. Vad händer när programmet körs? Ett (C as I1).M()-anrop

  1. Kör I1.M
  2. Kör I2.M
  3. Kastar någon form av körningsfel

Beslut: Gjort 2017-04-11: Kör I2.M, vilket är den mest specifika åsidosättningen vid körning.

Händelseåtkomster (stängda)

Stängt problem: Kan en händelse åsidosättas "bitvis"?

Tänk på det här fallet:

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

Den här "partiella" implementeringen av händelsen är inte tillåten eftersom syntaxen för en händelsedeklaration, precis som i en klass, inte bara tillåter en accessor. båda (eller ingetdera) måste tillhandahållas. Du kan göra samma sak genom att tillåta att den abstrakta borttagningsåtkomstorn i syntaxen implicit är abstrakt av avsaknaden av en brödtext:

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

Observera att detta är en ny (föreslagen) syntax. I den aktuella grammatiken har händelseåtkomster en obligatorisk kropp.

Stängt problem: Kan en händelseaccessor vara (implicit) abstrakt genom att utelämna en kropp, på samma sätt som metoder i gränssnitt och egenskapstillgångar är (implicit) abstrakta genom att utelämna en kropp?

beslut: (2017-04-18) Nej, händelsedeklarationer kräver båda konkreta tillgångsmetoder (eller ingen av dem).

Återabstraktion i en klass (stängd)

Stängt ärende: Vi bör bekräfta att detta är tillåtet (annars skulle det vara ett bakåtkompatibilitetsbrott att lägga till en standardimplementering).

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

Beslut: (2017-04-18) Ja, att lägga till en brödtext i en medlemsdeklaration för gränssnittet bör inte bryta C.

Förseglad överskrivning (avslutad)

Den föregående frågan förutsätter implicit att sealed-modifieraren kan tillämpas på en override i ett gränssnitt. Detta strider mot utkastet till specifikation. Vill vi tillåta att en åsidosättning förseglas? Käll- och binärkompatibilitetseffekter av tätning bör övervägas.

Avslutad fråga: Ska vi tillåta att låsa en åsidosättning?

Beslut: (2017-04-18) Låt oss inte tillåta sealed avseende på åsidosättningar i gränssnitt. Den enda användningen av sealed på interface-medlemmar är att göra dem icke-virtuella vid deras inledande deklaration.

Diamantarv och klasser (stängda)

Utkastet till förslaget föredrar klass åsidosättningar framför gränssnitts åsidosättningar i diamantrarvscenarier.

Vi kräver att varje gränssnitt och klass har en mest specifik åsidosättning för varje gränssnittsmetod bland de åsidosättningar som förekommer i typen eller dess direkta eller indirekta gränssnitt. Den mest specifika åsidosättningen är en unik åsidosättning som är mer specifik än alla andra åsidosättningar. Om det inte finns någon åsidosättning anses själva metoden vara den mest specifika åsidosättningen.

En åsidosättning M1 betraktas som mer specifik än en annan åsidosättning M2 om M1 deklareras på typ T1, M2 deklareras på typ T2, och antingen

  1. T1 innehåller T2 bland dess direkta eller indirekta gränssnitt, eller
  2. T2 är en gränssnittstyp men T1 är inte en gränssnittstyp.

Scenariot är detta

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?
    }
}

Vi bör bekräfta det här beteendet (eller besluta något annat)

Stängt problem: Bekräfta utkastspecifikationen ovan för mest specifika åsidosättning eftersom den gäller för blandade klasser och gränssnitt (en klass prioriteras framför ett gränssnitt). Se https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes.

Gränssnittsmetoder jämfört med structs (stängda)

Det finns vissa olyckliga interaktioner mellan standardgränssnittsmetoder och structs.

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

Observera att gränssnittsmedlemmar inte ärvs:

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

Därför måste klienten boxa structen för att anropa gränssnittsmetoder

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

Boxning på det här sättet motverkar de främsta fördelarna med en struct typ. Dessutom kommer alla mutationsmetoder inte att ha någon uppenbar effekt, eftersom de fungerar på en boxad kopia av structen:

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

Stängt problem: Vad kan vi göra åt detta:

  1. Förbjuda att en struct ärver en standardimplementering. Alla gränssnittsmetoder skulle behandlas som abstrakta i en struct. Sedan kan det ta tid senare att bestämma hur det ska fungera bättre.
  2. Kom med någon form av kodgenereringsstrategi som undviker boxning. I en metod som IB.Incrementskulle typen av this kanske likna en typparameter som är begränsad till IB. I samband med detta skulle icke-abstrakta metoder ärvas från gränssnitt för att undvika begränsning av anroparen. Detta kan öka implementeringen av kompilatorn och CLR avsevärt.
  3. Oroa dig inte för det och lämna det bara som en vårta.
  4. Andra idéer?

Beslut: Oroa dig inte för det och lämna det bara som en vårta. Se https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations.

Anrop till basgränssnitt (stängda)

Det här beslutet implementerades inte i C# 8. Syntaxen för base(Interface).M() implementeras inte.

Utkastspecifikationen föreslår en syntax för grundläggande gränssnittsanrop inspirerade av Java: Interface.base.M(). Vi måste välja en syntax, åtminstone för den första prototypen. Min favorit är base<Interface>.M().

Stängt problem: Vad är syntaxen för ett anrop till en basmedlem?

Beslut: Syntaxen är base(Interface).M(). Se https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. Gränssnittet som heter så måste vara ett basgränssnitt, men behöver inte vara ett direkt basgränssnitt.

Öppet problem: Ska grundläggande gränssnittsanrop tillåtas i klassmedlemmar?

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

Åsidosätta icke-offentliga gränssnittsmedlemmar (stängda)

I ett gränssnitt åsidosättas icke-offentliga medlemmar från basgränssnitt med hjälp av override-modifieraren. Om det är en "explicit" åsidosättning som namnger gränssnittet som innehåller medlemmen, utelämnas åtkomstmodifieraren.

Stängt ärende: Om det är en "implicit" åsidosättning som inte namnger gränssnittet, måste åtkomstmodifieraren matcha?

beslut: Endast offentliga medlemmar kan åsidosättas implicit och åtkomsten måste matcha. Se https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Öppet problem: Är åtkomstmodifieraren nödvändig, valfri eller utelämnad vid en explicit åsidosättning, till exempel override void IB.M() {}?

Öppet problem: Krävs override, eller är det valfritt eller utelämnas det vid en explicit åsidosättning, till exempel void IB.M() {}?

Hur implementerar man en icke-offentlig gränssnittsmedlem i en klass? Kanske måste det göras uttryckligen?

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

Stängt problem: Hur implementerar man en icke-offentlig gränssnittsmedlem i en klass?

Beslut: Du kan bara implementera icke-offentliga gränssnittsmedlemmar explicit. Se https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.

Beslut: Inget override nyckelord tillåts för gränssnittsmedlemmar. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

Binär kompatibilitet 2 (stängd)

Överväg följande kod där varje typ finns i en separat sammansättning

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

Vi förstår att implementeringen av I1.M i C är I2.M. Vad händer om sammansättningen som innehåller I3 ändras på följande sätt och omkompileras

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

men C är inte omkompilerad. Vad händer när programmet körs? Ett anrop av (C as I1).M()

  1. Kör I1.M
  2. Kör I2.M
  3. Kör I3.M
  4. Antingen 2 eller 3, deterministiskt
  5. Utlöser någon form av körningsundantag

Beslut: Utlöser ett undantag (5). Se https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods.

Vill du tillåta partial i gränssnittet? (stängd)

Med tanke på att gränssnitt kan användas på liknande sätt som abstrakta klasser används kan det vara användbart att deklarera dem partial. Detta skulle vara särskilt användbart när det gäller generatorer.

Förslag: Ta bort språkbegränsningen där gränssnitt och medlemmar i gränssnitt inte får deklareras partial.

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

Main i ett gränssnitt? (stängd)

Öppet problem: Är en static Main metod i ett gränssnitt en kandidat till att bli programmets startpunkt?

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

Bekräfta avsikten att stödja offentliga icke-virtuella metoder (stängda)

Kan vi bekräfta (eller ångra) vårt beslut att tillåta icke-virtuella offentliga metoder i ett gränssnitt?

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

Semi-Closed Problem: (2017-04-18) Vi tror att det kommer att vara användbart, men kommer tillbaka till det. Detta är ett hinder för att förstå den mentala modellen.

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

Introducerar en override i ett gränssnitt en ny medlem? (stängd)

Det finns några sätt att se om en åsidosättningsdeklaration introducerar en ny medlem eller inte.

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.
}

Öppet problem: Introducerar en åsidosättningsdeklaration i ett gränssnitt en ny medlem? (stängd)

I en klass är en övergripande metod "synlig" i vissa avseenden. Till exempel har namnen på dess parametrar företräde framför namnen på parametrarna i den åsidosatta metoden. Det kan vara möjligt att duplicera detta beteende i gränssnitt, eftersom det alltid finns en mest specifik åtgärd. Men vill vi duplicera det beteendet?

Dessutom är det möjligt att "åsidosätta" en åsidosättningsmetod? Diskutabel

Beslut: Inget override nyckelord tillåts för gränssnittsmedlemmar. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.

Egenskaper med en privat accessor (stängd)

Vi säger att privata medlemmar inte är virtuella och att kombinationen av virtuella och privata inte tillåts. Men hur är det med en fastighet med en privat accessor?

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

Är detta tillåtet? Är set-accessorn här virtual eller inte? Kan den åsidosättas där den är tillgänglig? Implementerar följande implicit bara get-åtkomstorn?

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

Är följande förmodligen ett fel eftersom IA.P.set inte är virtuellt och också eftersom det inte är tillgängligt?

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

Decision: Det första exemplet ser giltigt ut, medan det sista inte gör det. Detta löses på liknande sätt som det redan fungerar i C#. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor

Anrop för basgränssnitt, omgång 2 (stängd)

Detta implementerades inte i C# 8.

Vår tidigare "lösning" på hur man hanterar basanrop ger faktiskt inte tillräcklig uttrycksfullhet. Det visar sig att du i C# och CLR, till skillnad från Java, måste ange både gränssnittet som innehåller metoddeklarationen och platsen för den implementering som du vill anropa.

Jag föreslår följande syntax för basanrop i gränssnitt. Jag är inte kär i det, men det illustrerar vad någon syntax måste kunna uttrycka:

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
    }
}

Om det inte finns någon tvetydighet kan du skriva det mer enkelt

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
    }
}

Eller

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
    }
}

Eller

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
    }
}

beslut: Beslutade om base(N.I1<T>).M(s), medger att om vi har en anropsbindning kan det finnas problem här senare. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations

Varning för att struct inte implementerar standardmetoden? (stängd)

@vancem hävdar att vi allvarligt bör överväga att ta fram en varning om en värdetypsdeklaration inte kan åsidosätta någon gränssnittsmetod, även om den skulle ärva en implementering av metoden från ett gränssnitt. Eftersom det orsakar boxning och undergräver begränsade samtal.

Decision: Detta verkar vara något som passar bättre för en analysator. Det verkar också som om den här varningen kan vara bullrig, eftersom den skulle utlösas även om standardgränssnittsmetoden aldrig anropas och ingen boxning någonsin kommer att inträffa. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method

Statiska gränssnittskonstruktorer (stängda)

När körs statiska gränssnittskonstruktorer? Det aktuella CLI-utkastet föreslår att det inträffar när den första statiska metoden eller fältet används. Om det inte finns någon av dem så kanske det aldrig körs??

[2018-10-09 CLR-teamet föreslår "Kommer att spegla vad vi gör för värdetyper (cctor-kontroll vid tillgång till varje instansmetod)"]

Decision: Statiska konstruktorer körs också vid inmatning till instansmetoder, om den statiska konstruktorn inte beforefieldinit, i vilket fall statiska konstruktorer körs före åtkomst till det första statiska fältet. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run

Designa möten

2017-03-08 LDM Mötesanteckningar2017-03-21 LDM Mötesanteckningar2017-03-23 möte "CLR Betéende för Standardgränssnittsmetoder"2017-04-05 LDM Mötesanteckningar2017-04-11 LDM Mötesanteckningar2017-04-18 LDM Mötesanteckningar2017-04-19 LDM Mötesanteckningar2017-05-17 LDM Mötesanteckningar2017-05-31 LDM Mötesanteckningar2017-06-14 LDM Mötesanteckningar2018-10-17 LDM Mötesanteckningar2018-11-14 LDM Mötesanteckningar