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
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ěC
je přepsáná základní metoda určena prozkoumáním každé základní třídyC
počínaje přímou základní třídouC
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 jakoM
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íI
je 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 jakoM
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 instanceE
a přidruženým typem, který je typem vlastnosti. Je-liT
typ třídy, je přidružený typ vybrán z první deklarace nebo přepsání vlastnosti nalezené při počínání sT
a 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šímT
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í odT
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í meziT
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
aB
jsou metody a názvy, typy a formální seznamy parametrůA
aB
jsou stejné.A
aB
jsou vlastnosti, název a typA
aB
jsou stejné aA
má stejné přístupové objekty jakoB
(A
je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní).A
aB
jsou události a název a typA
aB
jsou stejné.A
aB
jsou indexery, seznamy typů a formálních parametrůA
aB
jsou stejné aA
má stejné přístupové objekty jakoB
(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
aB
jsou metody a jména a formální seznamy parametrůA
aB
jsou totožné, a návratový typA
je převoditelný na návratový typB
prostřednictvím identity implicitního odkazového převodu na návratový typB
.A
aB
jsou vlastnosti, názevA
aB
jsou stejné,A
má stejné přístupové objekty jakoB
(A
je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní), a typA
je převoditelný na návratový typB
prostřednictvím převodu identity, nebo pokudA
je vlastnost jen pro čtení, implicitní převod odkazu.A
aB
jsou události a název a typA
aB
jsou stejné.A
aB
jsou indexery, formální seznamy parametrůA
aB
jsou identické,A
má stejné přístupové objekty jakoB
(A
je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní), a typA
je převoditelný na návratový typB
prostřednictvím převodu identity nebo, pokudA
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 I3
byly metody I1.M()
a I2.M()
"sloučeny". Při implementaci I3
je 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
- některé diskuze na https://github.com/dotnet/roslyn/issues/357.
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-01-08.md
- Offline diskuze o rozhodnutí o podpoře přepsání metod tříd pouze v jazyce C# 9.0.
C# feature specifications