Delen via


Covariant resultaat

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 notities van de taalontwerpvergadering (LDM) .

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

Kampioenprobleem: https://github.com/dotnet/csharplang/issues/49

Samenvatting

Ondersteuning voor covariant retourtypen. Het is met name mogelijk om het overschrijven van een methode toe te staan om een meer afgeleid retourtype te declareren dan de methode die wordt overschreven, en om het overschrijven van een alleen-lezen eigenschap toe te staan om een meer afgeleid type te declareren. Overschrijvingsdeclaraties die in meer afgeleide typen optreden, moeten een retourtype opgeven dat ten minste zo specifiek is als dat in overschrijvingen in de basistypen voorkomt. Aanroepers van de methode of eigenschap ontvangen statisch het meer verfijnde retourtype van een oproep.

Motivatie

Het is een veelvoorkomend patroon in code dat verschillende methodes moeten worden bedacht om de beperking in de programmeertaal te omzeilen die vereist dat overrides hetzelfde type retourneren als de overschreven methode.

Dit is handig in het fabriekspatroon. In de Roslyn-codebasis hebben we bijvoorbeeld

class Compilation ...
{
    public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
    public override CSharpCompilation WithOptions(Options options)...
}

Gedetailleerd ontwerp

Dit is een specificatie voor covariant retourtypen in C#. Onze bedoeling is om het overschrijven van een methode toe te staan zodat deze een meer afgeleid retourtype retourneert dan de methode die het overschrijft, en om het overschrijven van een alleen-lezen eigenschap toe te staan zodat deze een meer afgeleid retourtype retourneert. Aanroepers van de methode of eigenschap ontvangen statisch het meer verfijnde retourtype van een aanroep, en overrides die voorkomen in meer afgeleide typen moeten een retourtype bieden dat ten minste zo specifiek is als dat in de overrides van hun basistypen.


Klassemethode overschrijven

De bestaande beperking op het overschrijven van klasses (§15.6.5 methoden)

  • De override-methode en de overschreven basismethode hebben hetzelfde retourtype.

is gewijzigd in

  • De override-methode moet een retourtype hebben dat converteerbaar is door een identiteitsconversie of (als de methode een waarde retourneert - niet een ref-return geeft zie §13.1.0.5) impliciete verwijzingsconversie naar het retourtype van de overschreven basismethode.

En de volgende aanvullende vereisten worden toegevoegd aan die lijst:

  • De overschrijvingsmethode moet een retourtype hebben dat converteerbaar is door een identiteitsconversie, of (als de methode een waarde retourneert - geen ref return, §13.1.0.5) impliciete referentieconversie naar het retourtype van elke overschrijving van de overschreden basismethode, die is gedeclareerd in een (direct of indirect) basistype van de overschrijvingsmethode.
  • Het retourtype van de overridemethode moet minstens zo toegankelijk zijn als de overridemethode (Toegankelijkheidsdomeinen - §7.5.3).

Met deze beperking wordt toegestaan dat een overridemethode in een private-klasse het retourtype private heeft. Er is echter een public overschrijvingsmethode vereist in een public type om een public retourtype te hebben.

Klasse-eigenschap en indexeerfunctie overschrijven

De bestaande beperking voor het overschrijven van klasse-eigenschappen (§15.7.6)

Een overschrijvingsverklaring van eigenschappen moet exact dezelfde toegankelijkheidsmodifiers en naam aangeven als de geërfde eigenschap, en er moet een identiteitsconversie zijn tussen het type van de overschrijvings- en de geërfde eigenschap. Als de overgenomen eigenschap slechts één toegangsfunctie heeft (bijvoorbeeld als de overgenomen eigenschap alleen-lezen of alleen-schrijven is), bevat de overschrijvende eigenschap alleen die toegangsfunctie. Als de overgenomen eigenschap beide accessors bevat (bijvoorbeeld als de overgenomen eigenschap lezen/schrijven is), kan de overschrijvende eigenschap één accessor of beide accessors bevatten.

is gewijzigd in

Een eigenschapsoverschrijvingsverklaring moet exact dezelfde toegankelijkheidsmodifiers en naam specificeren als de overgenomen eigenschap, en er moet een identiteitsconversie of (als de overgenomen eigenschap alleen-lezen is en een waarde retourneert - geen ref return§13.1.0.5) impliciete verwijzingsconversie zijn van het type van de overschrijvende eigenschap naar het type van de overgenomen eigenschap. Als de overgenomen eigenschap slechts één toegangsfunctie heeft (bijvoorbeeld als de overgenomen eigenschap alleen-lezen of alleen-schrijven is), bevat de overschrijvende eigenschap alleen die toegangsfunctie. Als de overgenomen eigenschap beide accessors bevat (bijvoorbeeld als de overgenomen eigenschap lezen/schrijven is), kan de overschrijvende eigenschap één accessor of beide accessors bevatten. Het type van de overschrijvende eigenschap moet minstens zo toegankelijk zijn als de overschrijvende eigenschap (Toegankelijkheidsdomeinen - §7.5.3).


De rest van de onderstaande ontwerpspecificatie stelt een verdere uitbreiding voor van covariant-retourtypen van interfacemethoden die op een later moment moeten worden overwogen.

Interfacemethode, eigenschap en indexeerfunctie overschrijven

Door de soorten leden uit te breiden die in een interface zijn toegestaan met de toevoeging van de DIM-functie in C# 8.0, voegen we ook ondersteuning toe voor override-leden, samen met covarante resultaten. Deze regels volgen de regels van override leden zoals gespecificeerd voor klassen, met de volgende verschillen:

De volgende tekst in klassen:

De methode die door een overschrijvingsdeclaratie wordt overschreven, wordt de overschreven basismethodegenoemd. Voor een override-methode M, gedeclareerd in een klasse C, wordt de overriden basismethode bepaald door elke basisklasse van Cte onderzoeken, te beginnen met de directe basisklasse van C en door te gaan met elke opeenvolgende directe basisklasse, totdat in een bepaald basisklassetype ten minste één toegankelijke methode wordt gevonden die dezelfde handtekening heeft als M na substitutie van typeargumenten.

wordt de bijbehorende specificatie voor interfaces gegeven:

De methode die door een overschrijvingsdeclaratie wordt overschreven, wordt de overschreven basismethodegenoemd. Voor een override-methode M die is gedeclareerd in een interface I, wordt de overschreven basismethode bepaald door de directe of indirecte basisinterfaces van Ite onderzoeken en de set interfaces te verzamelen die een toegankelijke methode declareren met dezelfde methodehandtekening als M na vervanging van typeargumenten. Als deze set interfaces een meest afgeleide typeheeft, waaruit een identiteit of impliciete verwijzingsconversie voor elk type in deze set mogelijk is en dat type een unieke methodeverklaring van dit soort bevat, dan is dat de overschreven basismethode.

We geven ook override eigenschappen en indexeerfuncties toe in interfaces zoals opgegeven voor klassen in §15.7.6 Virtual, verzegeld, overschrijven en abstracte accessors.

Naam opzoeken

Naamopzoeking in de aanwezigheid van klasse override-declaraties verandert op dit moment het resultaat van de naamopzoeking door details van het gevonden lid op te leggen vanuit de meest specifieke override-declaratie in de klassehiërarchie, beginnend vanaf het type van de kwalificatie van de identificator (of this wanneer er geen kwalificatie is). Bijvoorbeeld, in §12.6.2.2 Overeenkomende parameters we hebben

Voor virtuele methoden en indexeerfuncties die zijn gedefinieerd in klassen, wordt de parameterlijst gekozen uit de eerste declaratie of overschrijving van het functielid dat is gevonden bij het starten met het statische type van de ontvanger en het doorzoeken van de basisklassen.

we voegen aan deze toe

Voor virtuele methoden en indexeerfuncties die zijn gedefinieerd in interfaces, wordt de parameterlijst gekozen uit de declaratie of overschrijving van het functielid, gevonden in het meest afgeleide type onder de typen die de declaratie of overschrijving van het functielid bevatten. Het is een compilatiefout als er geen uniek type bestaat.

De bestaande tekst voor het resultaattype van een eigenschap of indexeerfunctie

  • Als I een exemplaareigenschap identificeert, is het resultaat een eigenschapstoegang met een bijbehorende exemplaarexpressie van E en een gekoppeld type dat het type van de eigenschap is. Als T een klassetype is, wordt het bijbehorende type gekozen uit de eerste declaratie of overschrijving van de eigenschap die is gevonden wanneer begonnen wordt met Ten door de basisklassen te zoeken.

is uitgebreid met

Als T een interfacetype is, wordt het bijbehorende type gekozen uit de declaratie of overschrijving van de eigenschap die is gevonden in de meest afgeleide versie van T of de directe of indirecte basisinterfaces. Het is een compilatiefout als er geen uniek type bestaat.

Er moet een soortgelijke wijziging worden aangebracht in §12.8.12.3 Indexertoegang

In §12.8.10 Aanroepexpressies breiden wij de bestaande tekst uit

  • Anders is het resultaat een waarde, met een type dat geassocieerd is met het retourtype van de methode of delegate. Als de aanroep van een instantiemethode is en de ontvanger van een klassetype Tis, wordt het bijbehorende type gekozen uit de eerste declaratie of onderdrukking van de methode die is gevonden bij het starten met T en het doorzoeken van de basisklassen.

met

Als de aanroep van een instantiemethode is en de ontvanger van een interfacetype Tis, wordt het bijbehorende type gekozen uit de declaratie of overschrijving van de methode die is gevonden in de meest afgeleide interface van T of een van zijn directe en indirecte basisinterfaces. Het is een compilatiefout als er geen uniek type bestaat.

Impliciete interface-implementaties

Deze sectie van de specificatie

Voor interfacetoewijzing komt een klasselid A overeen met een interfacelid B wanneer:

  • A en B zijn methoden, en de naam, het type en de formele parameterlijsten van A en B zijn identiek.
  • A en B eigenschappen zijn, de naam en het type A en B identiek zijn, en A heeft dezelfde toegangsrechten als B (A is toegestaan extra toegangsrechten te hebben als het geen expliciete implementatie van interfacelid is).
  • A en B zijn gebeurtenissen en de naam en het type A en B zijn identiek.
  • A en B indexeerfuncties zijn, het type en de formele parameterlijsten van A en B identiek zijn en A dezelfde toegangsrechten heeft als B (A is toegestaan extra toegangsrechten te hebben als het geen expliciete implementatie van interfacelid is).

wordt als volgt gewijzigd:

Voor interfacetoewijzing komt een klasselid A overeen met een interfacelid B wanneer:

  • A en B zijn methoden, en de naam- en formele parameterlijsten van A en B identiek zijn, en het retourtype van A is converteerbaar naar het retourtype van B via een identiteit van impliciete verwijzingsomzetting naar het retourtype van B.
  • A en B eigenschappen zijn, zijn de naam van A en B identiek, A heeft dezelfde toegangsrechten als B (A is toegestaan extra toegangsrechten te hebben als het geen expliciete interfacelid-implementatie is) en het type A converteerbaar is naar het retourtype B via een identiteitsconversie of, als A een leeseigenschap is, een impliciete verwijzingsconversie.
  • A en B zijn gebeurtenissen en de naam en het type A en B zijn identiek.
  • A en B indexeerfuncties zijn, zijn de formele parameterlijsten van A en B identiek, A heeft dezelfde toegangsrechten als B (A is toegestaan extra toegangsrechten te hebben als het geen expliciete implementatie van interfacelid is) en het type A is converteerbaar naar het retourtype B via een identiteitsconversie of, als A een alleen-lezen indexeerfunctie is, een impliciete verwijzingsconversie.

Dit is technisch gezien een brekende verandering, omdat in het onderstaande programma vandaag "C1.M" wordt afgedrukt, maar onder de voorgestelde herziening zou "C2.M" worden afgedrukt.

using System;

interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }
class Program
{
    static void Main()
    {
        I1 i = new C2();
        Console.WriteLine(i.M());
    }
}

Vanwege deze onderbrekende wijziging kunnen we overwegen om geen ondersteuning te bieden aan covariante retourtypen voor impliciete implementaties.

Beperkingen voor interface-implementatie

We hebben een regel nodig dat een expliciete interface-implementatie een retourtype moet declareren dat niet minder afgeleid mag zijn dan het retourtype dat is gedeclareerd in elke overschrijving in de basisinterfaces.

Gevolgen voor API-compatibiliteit

Nader te bepalen

Openstaande Problemen

De specificatie zegt niet hoe de beller het meer verfijnde retourtype krijgt. Waarschijnlijk zou dat op vergelijkbare wijze worden gedaan als de manier waarop aanroepers de parameterspecificaties van de meest afgeleide overschrijving verkrijgen.


Als we de volgende interfaces hebben:

interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); }

Houd er rekening mee dat in I3de methoden I1.M() en I2.M() zijn samengevoegd. Bij het implementeren van I3is het noodzakelijk om ze beide samen te implementeren.

Over het algemeen is een expliciete implementatie vereist om te verwijzen naar de oorspronkelijke methode. De vraag is, in een klas

class C : I1, I2, I3
{
    C IN.M();
}

Wat betekent dat hier? Wat moet N zijn?

Ik stel voor dat we de uitvoering van I1.M of I2.M (maar niet beide) toestaan en dat behandelen als een implementatie van beide.

Nadelen

  • Elke taalwijziging moet zichzelf terugverdienen.
  • [ ] We moeten ervoor zorgen dat de prestaties redelijk zijn, zelfs in het geval van uitgebreide overnamehiërarchieën
  • [] We moeten ervoor zorgen dat artefacten van de vertaalstrategie geen invloed hebben op taalsemantiek, zelfs wanneer nieuwe IL van oude compilers wordt gebruikt.

Alternatieven

We kunnen de taalregels enigszins versoepelen om, in bron, toe te staan,

// Possible alternative. This was not implemented.
abstract class Cloneable
{
    public abstract Cloneable Clone();
}

class Digit : Cloneable
{
    public override Cloneable Clone()
    {
        return this.Clone();
    }

    public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types
    {
        return this;
    }
}

Niet-opgeloste vragen

  • [ ] Hoe werken API's die zijn gecompileerd om deze functie te gebruiken in oudere versies van de taal?

Designvergaderingen