Sdílet prostřednictvím


Kovariantní vrácení

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách ze schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/49

Shrnutí

Podporují se kovariantní návratové typy. Konkrétně povolte, aby přepsání metody deklarovalo odvozenější návratový typ než metoda, kterou přepisuje, a podobně povolte, aby přepsání vlastnosti jen pro čtení deklarovalo více odvozený typ. Deklarace přepsání v odvozenějších typech musí poskytnout návratový typ přinejmenším tak specifický, jako se vyskytuje v přepsání u jejich základních typů. Volající metody nebo vlastnosti by staticky získali přesnější typ návratu při vyvolání.

Motivace

Je to běžný vzor v kódu, že různé názvy metod musí být vymyšleny, aby se obešlo omezení jazyka, které vyžaduje, aby přepisující metody vracely stejný typ jako přepsaná metoda.

To by bylo užitečné v modelu továrny. Například v základu kódu Roslyn bychom měli

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

Podrobný návrh

Toto je specifikace kovariantních návratových typů v jazyce C#. Naším záměrem je povolit, aby přepsání metody mohlo vrátit odvozenější návratový typ než metoda, kterou přepisuje, a podobně povolit, aby přepsání vlastnosti jen pro čtení mohlo vrátit odvozenější návratový typ. Volající metody nebo vlastnosti by staticky získali přesnější návratový typ z volání a přepsání, která se vyskytují ve více odvozených typech, by musela poskytnout návratový typ alespoň tak specifický, jaký se vyskytuje v přepsáních v jeho základních typech.


Přepsání metody třídy

Stávající omezení metod přepsání třídy (§15.6.5)

  • Metoda přepsání a přepsaná základní metoda mají stejný návratový typ.

je změněn na

  • Metoda přepsání musí mít návratový typ, který je konvertován převodem identity, nebo (pokud má metoda návratovou hodnotu – nikoli návratovou hodnotu odkaz viz §13.1.0.5 implicitní převod odkazu na návratový typ přepisované základní metody.

A k tomuto seznamu jsou připojeny následující další požadavky:

  • Metoda přepsání musí mít návratový typ, který je konvertován identitním převodem, nebo (pokud má metoda návratovou hodnotu a ne návrat odkaz, §13.1.0.5) implicitním převodem odkazu na návratový typ každého přepsání přepsané základní metody, která je deklarována v přímém nebo nepřímém základním typu metody přepsání.
  • Návratový typ metody přepsání musí být alespoň tak přístupný jako metoda přepsání (domény přístupnosti - §7.5.3).

Toto omezení umožňuje ve třídě private mít přepsanou metodu s návratovým typem private. Vyžaduje však, aby metoda přepsání public v typu public měla návratový typ public.

Přepsání vlastností Třídy a Indexeru

Stávající omezení vlastností při překrytí tříd (§15.7.6)

Deklarace přepsané vlastnosti určuje přesně tytéž modifikátory přístupnosti a název jako zděděná vlastnost a musí existovat identitní konverze mezi typem přepsané a zděděné vlastnosti. Pokud zděděná vlastnost má pouze jeden přístupový objekt (tj. pokud je zděděná vlastnost jen pro čtení nebo jen pro zápis), přepisovaná vlastnost musí obsahovat pouze tento přístupový objekt. Pokud zděděná vlastnost zahrnuje oba přístupové objekty (tj. pokud je zděděná vlastnost jen pro čtení i zápis), může přepsání zahrnovat buď jeden přístupový objekt, nebo oba přístupové objekty.

je změněn na

Deklarace přepisující vlastnost musí specifikovat přesně stejné modifikátory přístupnosti a jméno jako děděná vlastnost, a musí existovat převod identity nebo (pokud je děděná vlastnost jen pro čtení a vrací hodnotu, nikoli ref návrat§13.1.0.5), implicitní převod odkazu z typu přepisující vlastnosti na typ děděné vlastnosti. Pokud zděděná vlastnost má pouze jeden přístupový objekt (tj. pokud je zděděná vlastnost jen pro čtení nebo jen pro zápis), přepisovaná vlastnost musí obsahovat pouze tento přístupový objekt. Pokud zděděná vlastnost zahrnuje oba přístupové objekty (tj. pokud je zděděná vlastnost jen pro čtení i zápis), může přepsání zahrnovat buď jeden přístupový objekt, nebo oba přístupové objekty. Typ přepisující vlastnosti musí být alespoň tak přístupný jako přepisující vlastnost (domény přístupnosti - §7.5.3).


Zbývající část níže uvedené specifikace návrhu navrhuje další rozšíření kovariantních návratů metod rozhraní, které je třeba zvážit později.

Metoda rozhraní, vlastnost a přepsání indexeru

Rozšíření typů členů, které jsou povoleny v rozhraní s doplněním funkce DIM v jazyce C# 8.0, dále přidáváme podporu členů override spolu s kovariantními návraty. Následují pravidla členů override, jak jsou uvedena pro třídy, s následujícími rozdíly:

Následující text ve třídách:

Metoda přepsaná deklarací přepisování je známá jako přepsaná základní metoda . U metody přepsání M deklarované ve třídě Cje přepsáná základní metoda určena prozkoumáním každé základní třídy Cpočínaje přímou základní třídou C a pokračováním s každou následnou přímou základní třídou, dokud v daném typu základní třídy není umístěna alespoň jedna přístupná metoda, která má stejný podpis jako M po nahrazení argumentů typu.

má odpovídající specifikaci pro rozhraní:

Metoda přepsaná deklarací přepisování je známá jako přepsaná základní metoda . U metody přepsání M deklarované v rozhraní Ije přepsaná základní metoda určena zkoumáním veškerých přímých i nepřímých základních rozhraní I, kde se shromažďuje sada rozhraní deklarující přístupnou metodu, která má stejný podpis jako M po nahrazení argumentů typu. Pokud má tato sada rozhraní nejvíce odvozený typ , z něhož existuje identitní nebo implicitní převod odkazu z každého typu v této sadě, a tento typ obsahuje jedinečnou deklaraci takové metody, pak je to přepsaná základní metoda .

Podobně umožňujeme vlastnosti a indexery override v rozhraních, jak je specifikováno pro třídy v §15.7.6 Virtuální, zapečetěné, přepisované a abstraktní přístupy.

Vyhledávání názvů

Vyhledávání názvů v přítomnosti deklarací třídy override v současné době mění výsledek vyhledávání názvů tím, že ukládá podrobnosti nalezeného členu z nejvýraznější deklarace override v hierarchii třídy počínaje typem kvalifikátoru identifikátoru (nebo this, pokud neexistuje kvalifikátor). Máme například v §12.6.2.2 Odpovídající parametry

U virtuálních metod a indexerů definovaných ve třídách je seznam parametrů zvolen z první deklarace nebo přepsání člena funkce, začínaje statickým typem příjemce a prohledáním jeho základních tříd.

k tomu přidáme

U virtuálních metod a indexerů definovaných v rozhraních je seznam parametrů vybrán z deklarace nebo přepsání členu funkce, které jsou nalezeny v nejodvozenějším typu mezi těmi, které obsahují deklaraci přepsání členu funkce. Jedná se o chybu v době kompilace, pokud neexistuje žádný jedinečný typ.

Pro typ výsledku přístupu k vlastnosti nebo indexeru existující text

  • Pokud I identifikuje vlastnost instance, je výsledkem přístup k vlastnosti s přidruženým výrazem instance E a přidruženým typem, který je typem vlastnosti. Je-li T typ třídy, je přidružený typ vybrán z první deklarace nebo přepsání vlastnosti nalezené při počínání s Ta následném prohledávání jeho základních tříd.

je doplněn o

Pokud je T typ rozhraní, je příslušný typ zvolen z deklarace nebo přepsání vlastnosti nalezené v nejodvozenějším T nebo z jeho přímých či nepřímých základních rozhraní. Jedná se o chybu v době kompilace, pokud neexistuje žádný jedinečný typ.

Podobná změna by měla být provedena v §12.8.12.3 Přístup k indexeru

V §12.8.10 Vyvolání výrazů rozšiřujeme stávající text.

  • V opačném případě je výsledkem hodnota s přidruženým typem návratového typu metody nebo delegáta. Pokud je vyvolání instanční metody a příjemce je typu třídy T, přidružený typ je vybrán z první deklarace nebo přepsání nalezeného při zahájení od T a vyhledávání v jeho základních třídách.

s

Pokud je vyvolání instance metody a příjemce je typu rozhraní T, přidružený typ je vybrán z deklarace nebo přepsání metody nalezené v nejvíce odvozeném rozhraní mezi T a jeho přímými a nepřímými základními rozhraními. Jedná se o chybu v době kompilace, pokud neexistuje žádný jedinečný typ.

Implicitní implementace rozhraní

Tato část specifikace

Pro účely mapování rozhraní A člen třídy odpovídá členu rozhraní B v těchto případech:

  • A a B jsou metody a názvy, typy a formální seznamy parametrů A a B jsou stejné.
  • A a B jsou vlastnosti, název a typ A a B jsou stejné a A má stejné přístupové objekty jako B (A je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní).
  • A a B jsou události a název a typ A a B jsou stejné.
  • A a B jsou indexery, seznamy typů a formálních parametrů A a B jsou stejné a A má stejné přístupové objekty jako B (A může mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní).

se mění takto:

Pro účely mapování rozhraní A člen třídy odpovídá členu rozhraní B v těchto případech:

  • A a B jsou metody a jména a formální seznamy parametrů A a B jsou totožné, a návratový typ A je převoditelný na návratový typ B prostřednictvím identity implicitního odkazového převodu na návratový typ B.
  • A a B jsou vlastnosti, název A a B jsou stejné, A má stejné přístupové objekty jako B (A je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní), a typ A je převoditelný na návratový typ B prostřednictvím převodu identity, nebo pokud A je vlastnost jen pro čtení, implicitní převod odkazu.
  • A a B jsou události a název a typ A a B jsou stejné.
  • A a B jsou indexery, formální seznamy parametrů A a B jsou identické, A má stejné přístupové objekty jako B (A je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní), a typ A je převoditelný na návratový typ B prostřednictvím převodu identity nebo, pokud A je indexer jen pro čtení, implicitní převod odkazu.

Jedná se o technicky zásadní změnu, protože následující program dnes vytiskne "C1.M", ale pod navrhovanou revizí by vytiskl "C2.M".

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

Z důvodu této zásadní změny můžeme zvážit, že u implicitních implementací nebudeme podporovat kovariantní návratové typy.

Omezení implementace rozhraní

Budeme potřebovat pravidlo, že implementace explicitního rozhraní musí deklarovat návratový typ, který nesmí být méně odvozený než návratový typ deklarovaný u jakéhokoli přepsání ve svých základních rozhraních.

Důsledky kompatibility rozhraní API

TBD

Otevřené problémy

Specifikace neřekne, jak volající získá přesnější návratový typ. Předpokládá se, že by bylo provedeno podobným způsobem, jakým volající získají specifikace parametrů nejodvozenějšího přepsání.


Pokud máme následující rozhraní:

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

Všimněte si, že v I3byly metody I1.M() a I2.M() "sloučeny". Při implementaci I3je nutné je implementovat společně.

Obecně vyžadujeme explicitní implementaci odkazující na původní metodu. Otázka zní, v hodině.

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

Co to tady znamená? Co by mělo být N?

Navrhuji, abychom povolili implementaci I1.M nebo I2.M (ale ne obojího) a zacházíme s tím jako s implementací obou.

Nevýhody

  • [ ] Každá změna jazyka se musí sama zaplatit.
  • [ ] Měli bychom zajistit, aby byl výkon přiměřený, a to i v případě hierarchií hloubkové dědičnosti.
  • [ ] Měli bychom zajistit, aby artefakty strategie překladu neměly vliv na sémantiku jazyka, i když spotřebovávají nové IL ze starých kompilátorů.

Alternativy

Mohli bychom trochu uvolnit jazyková pravidla tak, aby to ve zdroji umožňovalo.

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

Nevyřešené otázky

  • [ ] Jak budou rozhraní API zkompilovaná pro použití této funkce fungovat ve starších verzích jazyka?

Designérské schůzky