Megosztás a következőn keresztül:


Kovariáns visszatérések

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

Bajnoki probléma: https://github.com/dotnet/csharplang/issues/49

Összefoglalás

Támogassa a kovariáns visszatérési típusokat. Engedélyezi egy metódus felülírását olyan módon, hogy a felülbírt metódusnál származtatottabb visszatérési típust deklaráljon, és hasonlóképpen engedélyezi egy írásvédett tulajdonság felülírását, hogy származtatottabb típust deklaráljon. A származtatottabb típusok esetében megjelenő felülbírálási deklarációknak legalább olyan specifikus visszatérési típust kell biztosítaniuk, mint ami az alaptípusok felülbírálásaiban jelenik meg. A metódus vagy tulajdonság hívói statikusan kapják meg a kifinomultabb visszatérési típust egy meghívásból.

Motiváció

A kódban gyakran előfordul, hogy különböző metódusneveket kell feltalálni a nyelvi kényszer megkerüléséhez, amelyet a felülbírálásoknak ugyanazt a típust kell visszaadnia, mint a felülbírált metódusnak.

Ez hasznos lehet a gyári mintában. Például a Roslyn-kódbázisban

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

Részletes kialakítás

Ez a C#-ban lévő specifikáció a kovariáns visszatérési típusokkal kapcsolatos. A szándékunk az, hogy egy metódus felülbírálása a felülbírált metódusnál származtatottabb visszatérési típust adjon vissza, és hasonlóképpen lehetővé teszi, hogy egy írásvédett tulajdonság felülbírálása származtatottabb visszatérési típust adjon vissza. A metódus vagy tulajdonság hívói statikusan kapják meg a kifinomultabb visszatérési típust egy meghívásból, és a származtatottabb típusok esetében megjelenő felülbírálásoknak legalább olyan konkrét visszatérési típust kell biztosítaniuk, mint ami az alaptípus felülbírálásaiban jelenik meg.


Osztálymetódus felülbírálása

Az osztály felülbírálásának meglévő korlátozása (§15.6.5) metódusok

  • A felülbírálási módszer és a felülbírált alapmetódus ugyanazzal a visszatérési típussal rendelkezik.

módosítva lett

  • A felülbírálási metódusnak olyan visszatérési típussal kell rendelkeznie, amely identitáskonvertálással konvertálható, vagy (ha a metódus értékvisszatérítéssel rendelkezik – nem ref visszatérési lásd 13.1.0.5. implicit referenciaátalakítást a felülbírált alapmetódus visszatérési típusára.

És a következő további követelmények hozzá vannak fűzve a listához:

  • A felülbírálási metódusnak olyan visszatérési típussal kell rendelkeznie, amely identitási konverzióval átalakítható, vagy ha a metódus értékvisszatérítéssel rendelkezik (és nem egy referencia visszatérítés,, §13.1.0.5) implicit referenciakonverzióval konvertálható a felülbírált alapmetódus minden, a felülbírálási metódus (közvetlen vagy közvetett) alaptípusában deklarált felülbírálásának visszatérési típusára.
  • A felülbírálási metódus visszatérési típusának legalább olyan akadálymentesnek kell lennie, mint a felülbírálási módszer (Akadálymentességi tartományok – §7.5.3).

Ez a korlátozás lehetővé teszi, hogy egy private osztály felülbírálási metódusa private visszatérési típussal rendelkezzen. Azonban szükség van egy public felülbírálási metódusra a public típusban, hogy public visszatérési típussal rendelkezzen.

Az osztály tulajdonságának és az indexelőnek a felülbírálása

Az osztályok felülbírálási korlátozásának meglévő tulajdonságai (§15.7.6)

A felülíró tulajdonságdeklarációnak pontosan ugyanazokat az akadálymentességi módosítókat és nevet kell megadnia, mint az örökölt tulajdonság, és identitásátalakítást kell a felülírás típusa és az örökölt tulajdonságközött. Ha az örökölt jellemzőnek csak egyetlen hozzáférője van (azaz ha az örökölt jellemző csak olvasható vagy csak írható), a felülíró jellemzőnek csak ezt a hozzáférőt kell tartalmaznia. Ha az örökölt tulajdonság mindkét tartozékot tartalmazza (azaz ha az örökölt tulajdonság írás-olvasás), a felülíró tulajdonság egyetlen tartozékot vagy mindkét tartozékot tartalmazhat.

módosítva lett

A felülíró tulajdonságdeklarációnak pontosan ugyanazokat a hozzáférési módosítókat és nevet kell megadnia, mint az öröklött tulajdonság, és identitásátalakításnak kell lennie , vagy (ha az örökölt tulajdonság írásvédett, és értéket ad vissza – nem ref visszatérés§13.1.0.5esetén) implicit referenciaátalakításnak kell lennie a felülíró tulajdonság típusáról az örökölt tulajdonság típusára. Ha az örökölt jellemzőnek csak egyetlen hozzáférője van (azaz ha az örökölt jellemző csak olvasható vagy csak írható), a felülíró jellemzőnek csak ezt a hozzáférőt kell tartalmaznia. Ha az örökölt tulajdonság mindkét tartozékot tartalmazza (azaz ha az örökölt tulajdonság írás-olvasás), a felülíró tulajdonság egyetlen tartozékot vagy mindkét tartozékot tartalmazhat. A felülíró tulajdonság típusának legalább olyan akadálymentesnek kell lennie, mint a felülíró tulajdonság (Akadálymentességi tartományok – 7.5.3.).


Az alábbi specifikációtervezet hátralevő része további bővítést javasol az interfészmódszerek ko-variáns visszatéréseire, amelyeket később megfontolnak.

Interfész metódus, tulajdonság és indexelő felülbírálása

A C# 8.0 dim funkcióval kiegészített felületen engedélyezett tagtípusokhoz hozzáadva a kovriant visszatérésekkel együtt override tagok támogatását is hozzáadjuk. Ezek az osztályokhoz megadott override tagokra vonatkozó szabályokat követik, az alábbi különbségekkel:

Az osztályokban a következő szöveg:

A felülbírálási deklaráció által felülbírált metódus az felülbírált alapmetódus. Egy Mosztályban deklarált felülbírálási C módszernél a felülbírált alapmetódus a Cminden alaposztályának vizsgálatával határozható meg, kezdve a C közvetlen alaposztályával, és az egyes egymást követő közvetlen alaposztályokkal folytatva, amíg egy adott alaposztálytípusban legalább egy olyan elérhető metódus található, amelynek aláírása megegyezik a típusargumentumok helyettesítése után M aláírásával.

az interfészek megfelelő specifikációját adja meg:

A felülbírálási deklaráció által felülbírált metódus az felülbírált alapmetódus. Egy felületen MIdeklarált felülbírálási módszer esetében a felülbírált alapmetódus a Iminden közvetlen vagy közvetett alapfelületének vizsgálatával határozható meg, összegyűjtve azokat az interfészeket, amelyek olyan elérhető módszert deklarálnak, amelynek aláírása a M-mal megegyezik a típusargumentumok helyettesítése után. Ha ez az illesztőkészlet rendelkezik egy legjobban származtatotttípussal, amelyhez a készlet minden típusában van azonosság- vagy implicit referenciaátalakítás, és ez a típus egyedi ilyen metódusdeklarációt tartalmaz, akkor ez a felüldefiniált alapmetódus.

Hasonlóképpen engedélyezzük a override tulajdonságokat és indexelőket az interfészekben, ahogy az az osztályoknál meghatározásra került a §15.7.6 virtuális, lezárt, felülbíráló és absztrakt hozzáférési metódusaira vonatkozóan.

Névkeresés

A névkeresés az osztály override deklarációk jelenlétében jelenleg úgy módosítja a névkeresés eredményét, hogy az azonosító minősítőjének típusától kiindulva, vagy ha nincs minősítő, akkor override-től kezdve az osztályhierarchiában az a leginkább származtatott this deklarációból származó tagrészletek kerüljenek alkalmazásra. Például a §12.6.2.2 Megfelelő paraméterek-ben.

Az osztályokban definiált virtuális metódusok és indexelők esetében a paraméterlista a függvénytag első deklarációjából vagy felülbírálásából lesz kiválasztva, amikor a fogadó statikus típusával kezdődik, és az alaposztályokon keresztül keres.

ehhez hozzáadjuk

Az interfészekben definiált virtuális metódusok és indexelők esetében a paraméterlista a függvénytag deklarációjából vagy a felülbírálás deklarációjából lesz kiválasztva, amely a legszármaztatottabb típusban található az olyan típusok közül, amelyek tartalmazzák a függvénytag felülbírálásának deklarálását. Fordítási időbeli hiba, ha nem létezik ilyen egyedi típus.

A tulajdonság vagy indexelő hozzáférés eredménytípusára vonatkozóan a meglévő szöveg a következő.

  • Ha I azonosít egy példánytulajdonságot, akkor az eredmény egy tulajdonsághozzáférés a E társított példánykifejezésével és egy társított típussal, amely a tulajdonság típusa. Ha T osztálytípus, akkor a társított típust a tulajdonság első deklarációjából vagy felülbírálásából választják ki, amelyet a T-től kezdődően keresnek az alaposztályokon keresztül.

ki van bővítve

Ha T egy interfésztípus, a társított típust a rendszer a T vagy közvetlen vagy közvetett alapfelületeinek leg származtatottabb tulajdonságában található tulajdonság deklarációjából vagy felülbírálásából választja ki. Fordítási időbeli hiba, ha nem létezik ilyen egyedi típus.

Hasonló módosítást kell tenni a 12.8.12.3 szakaszban az indexelő hozzáférésnél

A 12.8.10 meghívási kifejezésekben bővítjük a meglévő szöveget

  • Ellenkező esetben az eredmény egy érték, amely a metódus vagy delegált visszatérési típusának egy társított típusával rendelkezik. Ha a meghívás egy példány metódusra történik, és a fogadó osztálytípusú, T, akkor a társított típust a metódus első deklarációjából vagy felülírásából választják ki, miután a keresést T-nél elkezdik és végigmennek az alaposztályokon.

val

Ha a meghívás egy példánymetódus, és a fogadó Tinterfész típusú, akkor a rendszer a társított típust a T és közvetlen és közvetett alapfelületei közül a legszármozottabb interfészben található metódus deklarációjából vagy felülbírálásából választja ki. Fordítási időbeli hiba, ha nem létezik ilyen egyedi típus.

Implicit felület implementációi

A specifikáció ezen szakasza

Az interfész leképezése céljából egy osztálytag A akkor felel meg egy interfésztagnak B, ha:

  • A és B metódusok, és a A és B neve, típusa és formális paraméterlistái azonosak.
  • A és B tulajdonságok, a A és a B neve és típusa megegyezik, és A ugyanazokkal a tartozékokkal rendelkezik, mint B (A további tartozékokkal is rendelkezhet, ha nem explicit felülettag-implementáció).
  • A és B események, és a A és B neve és típusa azonos.
  • A és B indexelők, a A és B típus- és formális paraméterlistái azonosak, és A ugyanazokkal a tartozékokkal rendelkezik, mint B (A további tartozékokkal is rendelkezhet, ha nem explicit felülettag-implementáció).

az alábbiak szerint módosul:

Az interfész leképezése céljából egy osztálytag A akkor felel meg egy interfésztagnak B, ha:

  • A és B metódusok, és a A és B név- és formális paraméterlistái azonosak, és a A visszatérési típusa átalakítható a B visszatérési típusára az implicit hivatkozás konvertálásának identitásán keresztül a Bvisszatérési típusára.
  • A és B tulajdonságok, a A és a B neve azonos, A ugyanazokkal a tartozékokkal rendelkezik, mint B (A további tartozékokkal is rendelkezhet, ha nem explicit felülettag-implementáció), és a A típusa identitáskonvertálással átalakítható a B visszatérési típusára, vagy ha A olvasható tulajdonság, implicit referenciakonvertálás.
  • A és B események, és a A és B neve és típusa azonos.
  • A és B indexelők, a A és B formális paraméterlistái azonosak, A ugyanazokkal a tartozékokkal rendelkezik, mint B (A további tartozékokkal is rendelkezhet, ha nem explicit interfésztag-implementáció), és a A típusa identitásátalakítással átalakítható a B visszatérési típusára, vagy ha A egy olvasható indexelő, implicit referenciakonvertálás.

Ez technikailag egy kódtörő változás, mivel az alábbi program ma a „C1.M” szöveget írja ki, de a javasolt változtatás esetén a „C2.M” jelenne meg.

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

A kompatibilitástörő változás miatt megfontolhatjuk, hogy nem támogatjuk a kovariantikus visszatérési típusokat implicit implementációk esetében.

A felület implementálásának korlátozásai

Olyan szabályra lesz szükségünk, amely szerint az explicit interfész-implementációnak nem lehet alacsonyabb szintű visszatérési típusa, mint a visszatérési típus, amelyet bármelyik alapinterfész felülbírálásában deklarált.

API-kompatibilitási következmények

még meghatározandó

Nyitott kérdések

A specifikáció nem határozza meg, hogy a hívó hogyan kapja meg a pontosabb visszatérési típust. Feltehetően ez a folyamat ahhoz hasonló módon történik, ahogy a hívók megkapják a leginkább származtatott felülbírálás paraméterspecifikációit.


Ha a következő felületekkel rendelkezünk:

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

Vegye figyelembe, hogy I3a metódusok I1.M() és I2.M() "egyesítve" lettek. A I3megvalósításakor mindkettőt együtt kell megvalósítani.

Általában explicit implementációra van szükség az eredeti módszerre való hivatkozáshoz. A kérdés az, hogy egy osztályban

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

Mit jelent ez itt? Mi legyen N?

Azt javaslom, hogy engedélyezzük I1.M vagy I2.M (de mindkettőt nem), és ezt mindkettő megvalósításaként kezeljük.

Hátránya

  • [ ] Minden nyelvváltoztatásnak meg kell térülnie.
  • [] Biztosítanunk kell, hogy a teljesítmény ésszerű legyen, még a mély öröklési hierarchiák esetén is
  • [] Biztosítanunk kell, hogy a fordítási stratégia összetevői ne befolyásolják a nyelvi szemantikát, még akkor sem, ha régi fordítóktól származó új IL-t használnak.

Alternatívák

Kicsit lazíthatnánk a nyelvi szabályokon, hogy a forrásban

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

Megoldatlan kérdések

  • [ ] Hogyan működnek a funkció használatához lefordított API-k a nyelv régebbi verzióiban?

Tervezési értekezletek