výchozí metody rozhraní
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/52
Shrnutí
Přidání podpory pro virtuálních rozšiřujících metod – metody v rozhraních s konkrétními implementacemi. Třída nebo struktura, která implementuje takové rozhraní, musí mít jednu nejkonkrétnější implementaci pro metodu rozhraní, buď přímo implementovanou třídou nebo strukturou, nebo zděděnou z jejích základních tříd nebo rozhraní. Metody virtuálního rozšíření umožňují autorovi rozhraní API přidávat metody do rozhraní v budoucích verzích bez narušení zdrojové nebo binární kompatibility s existujícími implementacemi tohoto rozhraní.
Jsou podobnéjava
(Na základě pravděpodobné techniky implementace) tato funkce vyžaduje odpovídající podporu v rozhraní příkazového řádku nebo CLR. Programy, které tuto funkci využívají, se nedají spustit ve starších verzích platformy.
Motivace
Hlavní motivací pro tuto funkci jsou
- Výchozí metody rozhraní umožňují autorovi rozhraní API přidávat metody do rozhraní v budoucích verzích bez narušení zdrojové nebo binární kompatibility s existujícími implementacemi tohoto rozhraní.
- Tato funkce umožňuje c# spolupracovat s rozhraními API, která cílí na Android (Java) a iOS (Swift), která podporují podobné funkce.
- Jak se ukazuje, přidání výchozích implementací rozhraní poskytuje prvky jazykové funkce 'traits' (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Vlastnosti prokázaly, že jsou výkonnou programovací technikou (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf).
Podrobný návrh
Syntaxe rozhraní je rozšířena tak, aby umožňovala
- deklarace členů, které deklarují konstanty, operátory, statické konstruktory a vnořené typy;
- tělo metody nebo indexátoru, vlastnosti nebo přístupového objektu události (to znamená "výchozí" implementace);
- deklarace členů, které deklarují statická pole, metody, vlastnosti, indexery a události;
- deklarace členů používající syntaxi implementace explicitního rozhraní; a
- Modifikátory explicitního přístupu (výchozí přístup je
public
).
Členové s tělesy umožňují rozhraní poskytnout „výchozí“ implementaci metody ve třídách a strukturách, které neposkytují vlastní implementaci.
Rozhraní nemusí obsahovat stav instance. I když jsou teď statická pole povolená, pole instancí nejsou v rozhraních povolená. Automatické vlastnosti instance nejsou v rozhraních podporované, protože by implicitně deklarovaly skryté pole.
Statické a privátní metody umožňují užitečné refaktorování a uspořádání kódu použitého k implementaci veřejného API.
Přepsání metody v rozhraní musí používat explicitní syntaxi implementace rozhraní.
Deklarování typu třídy, struktury nebo výčtu v rámci parametru typu, který byl deklarován s použitím variance anotace , je chybou. Například deklarace C
níže je chyba.
interface IOuter<out T>
{
class C { } // error: class declaration within the scope of variant type parameter 'T'
}
Konkrétní metody v rozhraních
Nejjednodušší formou této funkce je možnost deklarovat v rozhraní konkrétní metodu, což je metoda s tělem.
interface IA
{
void M() { WriteLine("IA.M"); }
}
Třída, která implementuje toto rozhraní, nemusí implementovat jeho konkrétní metodu.
class C : IA { } // OK
IA i = new C();
i.M(); // prints "IA.M"
Konečné přetížení pro IA.M
ve třídě C
je konkrétní metoda M
definovaná v IA
. Všimněte si, že třída nedědí členy ze svých rozhraní; to není touto funkcí změněno:
new C().M(); // error: class 'C' does not contain a member 'M'
V rámci člena instance rozhraní this
má typ ohraničujícího rozhraní.
Modifikátory v rozhraních
Syntaxe rozhraní je upravená, aby umožnila použití modifikátorů na jeho členech. Jsou povoleny tyto možnosti: private
, protected
, internal
, public
, virtual
, abstract
, sealed
, static
, extern
a partial
.
Člen rozhraní, jehož deklarace obsahuje tělo, je člen virtual
, pokud není použit modifikátor sealed
nebo private
. Modifikátor virtual
lze použít u členu funkce, který by jinak byl implicitně virtual
. Podobně, i když abstract
je výchozí pro členy rozhraní bez těl, může být tento modifikátor uveden explicitně. Jiný než virtuální člen může být deklarován pomocí klíčového slova sealed
.
Je chybou, když člen funkce rozhraní private
nebo sealed
nemá tělo. Člen funkce private
nesmí mít modifikátor sealed
.
Modifikátory přístupu lze použít u členů rozhraní všech typů členů, které jsou povoleny. Úroveň přístupu public
je výchozí, ale může být explicitně udělena.
Otevřený problém: Musíme určit přesný význam modifikátorů přístupu, jako jsou
protected
ainternal
, a které deklarace je nepřepíší (v odvozeném rozhraní) nebo je implementují (ve třídě, která implementuje rozhraní).
Rozhraní mohou deklarovat static
členy, včetně vnořených typů, metod, indexerů, vlastností, událostí a statických konstruktorů. Výchozí úroveň přístupu pro všechny členy rozhraní je public
.
Rozhraní nemusí deklarovat konstruktory instancí, destruktory nebo pole.
Uzavřený problém: Mají být v rozhraní povoleny deklarace operátorů? Pravděpodobně nejde o operátory převodu, ale co ti ostatní? rozhodnutí: Operátory jsou povoleny s výjimkou pro operátory převodu, rovnosti a nerovnosti.
Uzavřený problém: Měli byste
new
povolit u deklarací členů rozhraní, které skryjí členy ze základních rozhraní? Rozhodnutí: Ano.
Uzavřený problém: v současné době nepovolujeme
partial
v rozhraní nebo jejích členech. To by vyžadovalo samostatný návrh. Rozhodnutí: Ano. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface
Explicitní implementace v rozhraních
Explicitní implementace umožňují programátorovi poskytnout nejvýraznější implementaci virtuálního člena v rozhraní, kde kompilátor nebo modul runtime by ho jinak nenalezl. Deklarace implementace je povolena explicitně implementovat konkrétní metodu základního rozhraní tím, že kvalifikuje deklaraci s názvem rozhraní (v tomto případě není povolen žádný modifikátor přístupu). Implicitní implementace nejsou povoleny.
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
}
Explicitní implementace v rozhraních nemusí být deklarovány sealed
.
Veřejné virtual
členy funkce v rozhraní je možné implementovat explicitně pouze v odvozeném rozhraní (kvalifikací názvu v deklaraci s typem rozhraní, který původně deklaroval metodu, a vynecháním modifikátoru přístupu). Prvek musí být přístupný, kde je implementován.
Reabstrakce
Virtuální metoda deklarovaná v rozhraní může být reabstrahována v odvozeném rozhraní.
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'.
Modifikátor abstract
je vyžadován v deklaraci IB.M
, aby označil, že IA.M
je opět abstrahován.
To je užitečné v odvozených rozhraních, kde je výchozí implementace metody nevhodná a vhodnější implementace by měla být poskytována implementací implementačních tříd.
Nejvýraznější pravidlo implementace
Vyžadujeme, aby každé rozhraní a třída měly nejspecifičtější implementaci pro každého virtuálního člena mezi implementacemi, které se vyskytují v typu nebo jeho přímých a nepřímých rozhraních. nejvýraznější implementace je jedinečná implementace, která je konkrétnější než každá jiná implementace. Pokud neexistuje žádná implementace, člen samotný se považuje za nejvýraznější implementaci.
Jedna M1
implementace se považuje za konkrétnější než jiná M2
implementace, pokud je M1
deklarován u typu T1
, M2
je deklarován u typu T2
a buď
-
T1
obsahujeT2
mezi svými přímými nebo nepřímými rozhraními -
T2
je typ rozhraní, aleT1
není typem rozhraní.
Například:
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'
Nejspecifičtější pravidlo implementace zajišťuje, že konflikt (tj. nejednoznačnost vyplývající z tzv. diamantové dědičnosti) je explicitně vyřešen programátorem v místě, kde konflikt vzniká.
Protože podporujeme explicitní reabstrakce v rozhraních, mohli bychom to zavést i ve třídách.
abstract class E : IA, IB, IC // ok
{
abstract void IA.M();
}
uzavřený problém: měli bychom podporovat explicitní implementace abstraktních rozhraní ve třídách? Rozhodnutí: NE
Kromě toho je to chyba, pokud v deklaraci třídy je nejkonkrétnější implementace nějaké metody rozhraní abstraktní implementace, která byla deklarována v rozhraní. Jedná se o existující pravidlo restované pomocí nové terminologie.
interface IF
{
void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'
Je možné, že virtuální vlastnost deklarovaná v rozhraní má svou nejkonkrétnější implementaci pro svůj get
přístupový objekt v jednom rozhraní a svou nejkonkrétnější implementaci pro svůj set
přístupový objekt v jiném rozhraní. To se považuje za porušení pravidla nejkonkrétnější implementace
metody static
a private
Vzhledem k tomu, že rozhraní teď můžou obsahovat spustitelný kód, je užitečné abstrahovat běžný kód do privátních a statických metod. Teď je povolíme v rozhraních.
uzavřený problém: Měli bychom podporovat privátní metody? Měli bychom podporovat statické metody? rozhodnutí: ANO
otevřít problém: měli bychom povolit, aby metody rozhraní mohly být
protected
nebointernal
nebo jiný druh přístupu? Pokud ano, jaké jsou sémantiky? Jsou ve výchozím nastavenívirtual
? Pokud ano, existuje způsob, jak je nastavit jako ne virtuální?
uzavřený problém: Pokud podporujeme statické metody, měli bychom podporovat (statické) operátory? rozhodnutí: ANO
Vyvolání základního rozhraní
Syntaxe v této části nebyla implementována. Zůstává aktivním návrhem.
Kód typu odvozeného z rozhraní s výchozí metodou může explicitně vyvolat "základní" implementaci rozhraní.
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(); }
}
Instanční (ne statická) metoda je povolena vyvolat nevirtuálně implementaci přístupné instanční metody v přímém základním rozhraní pojmenováním pomocí syntaxe base(Type).M
. To je užitečné v případě, že přepsání, které je nutné poskytnout kvůli dědičnosti diamantů, je vyřešeno delegováním na jednu konkrétní základní implementaci.
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(); }
}
Při přístupu k virtual
nebo abstract
členovi pomocí syntaxe base(Type).M
je nutné, aby Type
obsahoval jedinečný nejvýraznější přepsání pro M
.
Závazné základní klauzule
Rozhraní teď obsahují typy. Tyto typy mohou být použity v základní klauzuli jako základní rozhraní. Při navazování základní klauzule může být potřeba znát sadu základních rozhraní pro vázání těchto typů (např. pro vyhledávání v nich a řešení chráněného přístupu). Význam základní klauzule rozhraní je tedy cyklicky definován. Pokud chcete cyklus přerušit, přidáme nová pravidla jazyka odpovídající podobnému pravidlu, které je již zavedeno pro třídy.
Při určování významu interface_base rozhraní se dočasně předpokládá, že základní rozhraní jsou prázdná. Intuitivně to zajišťuje, že význam základní klauzule nemůže rekurzivně záviset na sobě.
Použili jsme následující pravidla:
"Když třída B pochází z třídy A, jedná se o chybu v době kompilace, která má A záviset na B. Třída přímo závisí na své přímé základní třídě (pokud existuje) a přímo závisí na třídy , ve které je okamžitě vnořena (pokud existuje). Vzhledem k této definici je úplná sada tříd , na kterých třída závisí, reflexní a tranzitivní uzavření přímo závisí na vztahu."
Je chybou při kompilaci, když se rozhraní pokouší přímo nebo nepřímo dědit samo ze sebe. Základní rozhraní rozhraní jsou explicitní základní rozhraní a jejich základní rozhraní. Jinými slovy, množina základních rozhraní je úplné tranzitivní uzavření explicitních základních rozhraní, jejich explicitních základních rozhraní a tak dále.
Upravujeme je takto:
Když třída B je odvozena od třídy A, je chybou při kompilaci, když A závisí na B. Třída přímo závisí na její přímé základní třídě (pokud existuje) a přímo závisí na typu , ve kterém je okamžitě vnořena (pokud existuje).
Když rozhraní IB rozšiřuje rozhraní IA, jedná se o chybu při kompilaci, pokud IA závisí na IB. Rozhraní přímo závisí na jeho přímých základních rozhraní (pokud existuje) a přímo závisí na typu, ve kterém je bezprostředně vnořena (pokud existuje).
Vzhledem k těmto definicům je úplná sada typů , na kterých typ závisí, reflexní a tranzitivní uzavření přímo závisí na vztahu.
Vliv na stávající programy
Zde uvedená pravidla nemají žádný vliv na význam stávajících programů.
Příklad 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
}
Příklad 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
}
Stejná pravidla poskytují podobné výsledky podobné situaci zahrnující výchozí metody rozhraní:
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
}
uzavřený problém: ověřte, že se jedná o zamýšlený výsledek specifikace. rozhodnutí: ANO
Řešení metod modulu runtime
Uzavřený problém: Specifikace by měla popisovat algoritmus řešení metod modulu runtime v případě výchozích metod rozhraní. Musíme zajistit, aby sémantika byla konzistentní se sémantikou jazyka, například které deklarované metody přepíší nebo neimplementují
internal
metodu.
Rozhraní API pro podporu CLR
Aby kompilátory mohly zjistit, kdy kompilují modul runtime, který tuto funkci podporuje, knihovny pro takové moduly runtime jsou upraveny tak, aby inzerovaly tento fakt prostřednictvím rozhraní API probíraného v https://github.com/dotnet/corefx/issues/17116. Přidáme
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
// Presence of the field indicates runtime support
public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
}
}
otevřený problém: Je to nejlepší název pro funkci CLR? Funkce CLR dělá mnohem víc než jen to (například zmírňuje omezení ochrany, podporuje přetěžování v rozhraních atd.). Možná by se mělo jmenovat něco jako "konkrétní metody v rozhraních" nebo "vlastnosti"?
Další oblasti, které je třeba zadat
- Bylo by užitečné katalogovat druhy účinků zdrojové a binární kompatibility způsobených přidáním výchozích metod rozhraní a přepsání do existujících rozhraní.
Nevýhody
Tento návrh vyžaduje koordinovanou aktualizaci specifikace CLR (pro podporu konkrétních metod v rozhraních a řešení metod). Je proto poměrně "nákladný" a může být vhodné provést v kombinaci s dalšími funkcemi, které také očekáváme, že by vyžadovaly změny CLR.
Alternativy
Žádný.
Nevyřešené otázky
- Otevřené otázky jsou uvedené v celém návrhu výše.
- Seznam otevřených otázek najdete také v https://github.com/dotnet/csharplang/issues/406.
- Podrobná specifikace musí popsat mechanismus řešení používaný za běhu k výběru přesné metody, která se má vyvolat.
- Podrobnou práci s metadaty generovanými novými kompilátory a využívanými staršími kompilátory je potřeba podrobně provést. Musíme například zajistit, aby reprezentace metadat, kterou používáme, nezpůsobila přidání výchozí implementace v rozhraní k přerušení existující třídy, která implementuje toto rozhraní při kompilaci starším kompilátorem. To může mít vliv na reprezentaci metadat, kterou můžeme použít.
- Návrh musí zvážit spolupráci s jinými jazyky a existujícími kompilátory pro jiné jazyky.
Vyřešené otázky
Abstraktní přebití
Předchozí návrh specifikace obsahoval možnost přeabstrahovat zděděné metody:
interface IA
{
void M();
}
interface IB : IA
{
override void M() { }
}
interface IC : IB
{
override void M(); // make it abstract again
}
Moje poznámky pro 2017-03-20 ukázaly, že jsme se rozhodli to nepovolit. Existují však alespoň dva případy použití:
- Rozhraní JAVA API, se kterými někteří uživatelé této funkce chtějí spolupracovat, závisí na tomto zařízení.
- Programování s vlastnostmi z toho přináší výhody. Reabstraction je jedním z prvků jazykové funkce „traits“ (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Následující je dovoleno u tříd:
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
}
Tento kód bohužel nelze refaktorovat jako sadu rozhraní (vlastností), pokud to není povoleno. Podle Jaredova principu chamtivostiby to mělo být povoleno.
Uzavřený problém: Měla by být reabstraction povolena? [ANO] Moje poznámky byly špatně. Poznámky LDM uvádějí, že v rozhraní je povolena reabstrakce. Ne ve třídě.
Virtuální modifikátor vs. zapečetěný modifikátor
Od Aleksey Tsingauz:
Rozhodli jsme se povolit modifikátory výslovně uvedené na členech rozhraní, pokud není důvod zakázat některé z nich. To přináší zajímavou otázku týkající se virtuálního modifikátoru. Měla by být vyžadována u členů s výchozí implementací?
Mohli bychom říct, že:
- Pokud neexistuje žádná implementace a není uvedeno, že je člen virtuální nebo zapečetěný, předpokládáme, že je abstraktní.
- pokud existuje implementace a není zadána abstraktní ani zapečetěná, předpokládáme, že člen je virtuální.
- zapečetěný modifikátor je nutný k tomu, aby metoda nebyla virtuální ani abstraktní.
Případně můžeme říci, že virtuální modifikátor je nutný pro virtuálního člena. To znamená, že pokud existuje člen s implementací, která není explicitně označena virtuálním modifikátorem, není virtuální ani abstraktní. Tento přístup může poskytovat lepší prostředí při přesunu metody z třídy do rozhraní:
- abstraktní metoda zůstává abstraktní.
- virtuální metoda zůstane virtuální.
- metoda bez jakéhokoli modifikátoru zůstává virtuální ani abstraktní.
- zapečetěný modifikátor nelze použít u metody, která není přepsána.
Jak to myslíš?
Uzavřený problém: By měla být konkrétní metoda (s implementací) implicitně
virtual
? [ANO]
Rozhodnutí: přijatá v rámci LDM dne 2017-04-05
- ne-virtuální by měly být explicitně vyjádřeny prostřednictvím
sealed
neboprivate
. -
sealed
je klíčové slovo pro vytvoření instančních členů rozhraní s nevirtuálními těly. - Chceme povolit všechny modifikátory v rozhraních.
- Výchozí přístupnost členů rozhraní je veřejná, včetně vnořených typů
- Privátní funkční členy v rozhraních jsou implicitně zapečetěné a
sealed
není na nich povoleno. - Soukromé třídy (v rozhraních) jsou povoleny a mohou být zapečetěny a to znamená zapečetěné ve smyslu třídy zapečetěné.
- Při absenci dobrého návrhu nejsou částečné úpravy povoleny na rozhraních ani jejich členech.
Binární kompatibilita 1
Když knihovna poskytuje výchozí implementaci
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}
Chápeme, že implementace I1.M
v C
je I1.M
. Co když se sestavení obsahující I2
změní následujícím způsobem a znovu zkompiluje
interface I2 : I1
{
override void M() { Impl2 }
}
ale C
není rekompilován. Co se stane, když se program spustí? Vyvolání (C as I1).M()
- Spouští
I1.M
- Spouští
I2.M
- Vyvolá určitý typ chyby za běhu.
rozhodnutí: učiněno 2017-04-11: Spouští I2.M
, což je jednoznačně nejkonkrétnější přepsání za běhu.
Přístupové objekty událostí (uzavřeno)
Uzavřený problém: Lze událost přepsat "po částech"?
Vezměte v úvahu tento případ:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
// error: "remove" accessor missing
}
}
Tato "částečná" implementace události není povolena, protože jako ve třídě syntaxe deklarace události neumožňuje pouze jeden přístup; musí být k dispozici oba (nebo ani jeden). Stejného výsledku můžete dosáhnout tím, že povolíte, aby abstraktní přístupový objekt pro odebrání v syntaxi byl implicitně abstraktován absencí těla:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
remove; // implicitly abstract
}
}
Všimněte si, že je to nová (navržená) syntaxe. V aktuální gramatikě mají přístupové objekty událostí povinný text.
Uzavřený problém: Může být přístup k události (implicitně) abstraktní vynecháním těla, podobně jako metody v rozhraních a přístupových objektech vlastností (implicitně) abstraktní vynecháním těla?
rozhodnutí: (2017-04-18) Ne, deklarace událostí vyžadují buď obě konkrétní přistupové metody, nebo žádnou.
Reabstraction ve třídě (uzavřeno)
Uzavřený problém: Měli bychom potvrdit, že je tato možnost povolená (jinak by přidání výchozí implementace byla zásadní změnou):
interface I1
{
void M() { }
}
abstract class C : I1
{
public abstract void M(); // implement I1.M with an abstract method in C
}
rozhodnutí: (2017-04-18) Ano, přidání těla do deklarace člena rozhraní by nemělo narušit C.
Zapečetěné přepsání (uzavřeno)
Předchozí otázka implicitně předpokládá, že modifikátor sealed
lze použít na override
v rozhraní. To je v rozporu se specifikací návrhu. Chceme povolit zapečetění? Je třeba zvážit účinky utěsnění na kompatibilitu zdrojového kódu a binární kompatibilitu.
uzavřený problém: Měli bychom povolit zatavení přepsání?
rozhodnutí: (2017-04-18) Nepovolme použití sealed
v rozhraních. Jediným použitím sealed
na členech rozhraní je, aby byly v jejich počáteční deklaraci nevirtuální.
Dědičnost a třídy diamantové (uzavřené)
Návrh upřednostňuje přepsání tříd před přepsáním rozhraní ve scénářích diamantové dědičnosti.
Vyžadujeme, aby každé rozhraní a třída měly nejkonkrétnější přepsání pro každou metodu z rozhraní, které se objevují v daném typu nebo v jeho přímých a nepřímých rozhraních. nejkonkrétnější přepsání je jedinečné přepsání, které je konkrétnější než jakékoliv jiné. Pokud neexistuje žádné přetížení, samotná metoda se považuje za nejvíce specifické přetížení.
Jeden
M1
přepsání se považuje za konkrétnější než jinéM2
přepsání, pokud jeM1
deklarován u typuT1
,M2
je deklarován u typuT2
a buď
T1
obsahujeT2
mezi svými přímými nebo nepřímými rozhranímiT2
je typ rozhraní, aleT1
není typem rozhraní.
Jedná se o tento scénář.
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?
}
}
Toto chování bychom měli potvrdit (nebo rozhodnout jinak).
Uzavřený problém: Potvrďte specifikaci konceptu výše pro nejvýkonnější přepsání, protože se vztahuje na smíšené třídy a rozhraní (třída má přednost před rozhraním). Viz https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes.
Metody rozhraní vs. struktury (uzavřeno)
Existují některé nešťastné interakce mezi výchozími metodami rozhraní a strukturami.
interface IA
{
public void M() { }
}
struct S : IA
{
}
Všimněte si, že členové rozhraní se nedědí.
var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'
Klient proto musí vytvořit strukturu pro vyvolání metod rozhraní.
IA s = default(S); // an S, boxed
s.M(); // ok
Boxing tímto způsobem překoná hlavní výhody typu struct
. Kromě toho žádné metody mutace nebudou mít žádný zjevný účinek, protože pracují na zabalené kopii struktury.
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
Uzavřený problém: Co můžeme udělat s tímto problémem:
- Zakázat, aby
struct
zdědil(a) výchozí implementaci. Všechny metody rozhraní by byly považovány za abstraktní vstruct
. Později se můžeme rozhodnout, jak to zlepšit.- Vymyslete si strategii generování kódu, která se vyhne boxování. Uvnitř metody, jako je
IB.Increment
, typthis
by mohl být podobný parametru typu omezenému naIB
. S tím souvisí, že aby se zabránilo omezení volajícího, by se zdědily neabstraktní metody z rozhraní. To může výrazně zvýšit práci kompilátoru a implementace CLR.- Nebojte se toho a prostě to nechte jako bradavici.
- Další nápady?
Rozhodnutí: Nestarat se o to a prostě ho nechat jako bradavici. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations.
Vyvolání základního rozhraní (uzavřeno)
Toto rozhodnutí nebylo implementováno v jazyce C# 8. Syntaxe base(Interface).M()
není implementována.
Koncept specifikace navrhuje syntaxi pro vyvolání základního rozhraní inspirované Javou: Interface.base.M()
. Musíme vybrat syntaxi aspoň pro počáteční prototyp. Moje oblíbená je base<Interface>.M()
.
Uzavřený problém: Jaká je syntaxe vyvolání základního člena?
Rozhodnutí: Syntaxe je base(Interface).M()
. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation. Rozhraní tak pojmenované musí být základní rozhraní, ale nemusí být přímým základním rozhraním.
Otevřená otázka: Měla by být ve členech třídy povolena volání základního rozhraní?
Rozhodnutí: Ano. https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation
Přepsání členů neveřejného rozhraní (uzavřeno)
V rozhraní se neveřejné členy přepíší ze základních rozhraní pomocí modifikátoru override
. Pokud se jedná o explicitní přepsání, které určuje rozhraní, které obsahuje člena, je modifikátor přístupu vynechán.
Uzavřený problém: Pokud se jedná o implicitní přepsání, které nenázví rozhraní, musí se modifikátor přístupu shodovat?
rozhodnutí: Pouze veřejní členové mohou být implicitně přepsáni a přístupová práva se musí shodovat. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.
Otevřený problém: Je modifikátor přístupu povinný, volitelný nebo vynechaný u explicitního přepsání, například
override void IB.M() {}
?
Otevřená otázka: je
override
povinné, volitelné nebo vypuštěno u explicitního přepsání, jako je napříkladvoid IB.M() {}
?
Jak implementovat neveřejný člen rozhraní v rámci třídy? Možná to musí být provedeno explicitně?
interface IA
{
internal void MI();
protected void MP();
}
class C : IA
{
// are these implementations? Decision: NO
internal void MI() {}
protected void MP() {}
}
Uzavřený problém: Jak implementovat člena neveřejného rozhraní ve třídě?
Rozhodnutí: Můžete explicitně implementovat pouze neveřejné členy rozhraní. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list.
rozhodnutí: Pro členy rozhraní není povoleno žádné klíčové slovo override
. 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ární kompatibilita 2 (uzavřená)
Vezměte v úvahu následující kód, ve kterém je každý typ v samostatném sestavení.
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}
Chápeme, že implementace I1.M
v C
je I2.M
. Co když se sestavení obsahující I3
změní následujícím způsobem a znovu zkompiluje
interface I3 : I1
{
override void M() { Impl3 }
}
ale C
není rekompilován. Co se stane, když se program spustí? Vyvolání (C as I1).M()
- Spouští
I1.M
- Spouští
I2.M
- Spouští
I3.M
- Buď 2, nebo 3, deterministicky
- Vyvolá nějaký druh výjimky modulu runtime.
rozhodnutí: Vyvolání výjimky (5). Viz https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods.
Povolit partial
v rozhraní? (uzavřeno)
Vzhledem k tomu, že rozhraní mohou být použita způsobem analogickým k tomu, jak jsou používány abstraktní třídy, může být užitečné je deklarovat partial
. To by bylo užitečné zejména v případě generátorů.
Návrh: Odebrat omezení jazyka, že rozhraní a členové rozhraní nesmí být deklarovány
partial
.
Rozhodnutí: Ano. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface.
Main
v rozhraní? (uzavřeno)
Otevřený problém: Je metoda
static Main
v rozhraní kandidátem na vstupní bod programu?
Rozhodnutí: Ano. Viz https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface.
Potvrzení záměru podporovat veřejné ne virtuální metody (uzavřeno)
Můžeme potvrdit (nebo vrátit zpět) naše rozhodnutí o povolení ne-virtuálních veřejných metod v rozhraní?
interface IA
{
public sealed void M() { }
}
Semi-Closed Problém: (2017-04-18) Myslíme si, že to bude užitečné, ale vrátíme se k němu. Jedná se o mentální model, který způsobuje zmatení.
Rozhodnutí: Ano. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods.
Představuje override
v rozhraní nový člen? (uzavřeno)
Existuje několik způsobů, jak zjistit, jestli deklarace přepsání zavádí nového člena, nebo ne.
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.
}
Otevřený problém: Zavádí deklarace přepsání v rozhraní nový člen? (uzavřeno)
Ve třídě je metoda překrytí "viditelná" v některých ohledech. Například názvy jeho parametrů mají přednost před názvy parametrů v přepsáné metodě. Je možné chování v rozhraních napodobit, protože vždy existuje konkrétní přepsání. Ale chceme toto chování duplikovat?
Je také možné "přepsat" již přepsanou metodu? Sporný
rozhodnutí: Pro členy rozhraní není povoleno žádné klíčové slovo override
.
https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member.
Vlastnosti s privátním příslušenstvím (uzavřeno)
Říkáme, že soukromé členy nejsou virtuální a kombinace virtuálních a soukromých je zakázána. Ale co nemovitost s privátním příslušenstvím?
interface IA
{
public virtual int P
{
get => 3;
private set { }
}
}
Je to povolené? Je tady set
příslušenství virtual
nebo ne? Je možné ji přepsat tam, kde je přístupná? Implementuje následující objekt implicitně pouze přístup get
?
class C : IA
{
public int P
{
get => 4;
set { }
}
}
Je následující pravděpodobně chyba, protože IA. P.set není virtuální a také proto, že není přístupný?
class C : IA
{
int IA.P
{
get => 4;
set { } // Decision: Not valid
}
}
rozhodnutí: První příklad vypadá jako platný, zatímco poslední není. Je to vyřešeno podobně jako to, jak už funguje v jazyce C#. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor
Vyvolání základního rozhraní, kolo 2 (uzavřeno)
To nebylo implementováno v jazyce C# 8.
Naše předchozí "řešení" pro zpracování základních vyvolání ve skutečnosti neposkytuje dostatečnou výraznost. Ukázalo se, že v jazyce C# a CLR na rozdíl od Javy musíte zadat rozhraní obsahující deklaraci metody a umístění implementace, kterou chcete vyvolat.
Navrhuji následující syntaxi pro základní volání v rozhraních. Nejsem v lásce s ním, ale ilustruje, co jakákoli syntaxe musí být schopna vyjádřit:
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
}
}
Pokud neexistuje nejednoznačnost, můžete ji napsat jednodušeji.
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
}
}
Nebo
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
}
}
Nebo
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
}
}
Rozhodnutí: Rozhodli jsme se o base(N.I1<T>).M(s)
, připouštějíc, že pokud máme vyvolání vazby, může se zde později vyskytnout problém. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations
Varování: Struktura neimplementuje výchozí metodu? (uzavřeno)
@vancem tvrdí, že bychom měli vážně zvážit vytvoření upozornění, pokud deklarace typu hodnoty nepřepíše některé metody rozhraní, i když by zdědila implementaci této metody z rozhraní. Protože to způsobuje zapouzdření a podkopává omezená volání.
rozhodnutí: Tohle vypadá jako něco vhodnějšího pro analýzu. Zdá se také, že toto upozornění může být rušivé, protože by se aktivovalo, i když výchozí metoda rozhraní není nikdy volána a k boxování nikdy nedojde. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method
Statické konstruktory rozhraní (uzavřené)
Kdy jsou spuštěny statické konstruktory rozhraní? Aktuální koncept rozhraní příkazového řádku navrhuje, aby k němu došlo při přístupu k první statické metodě nebo poli. Pokud ani jeden z nich není, nemusí být nikdy spuštěn??
[2018-10-09 Tým CLR navrhuje "Bude zrcadlit to, co děláme u hodnotových typů (kontrola přístupu ke každé metodě instance v cctoru)"]
rozhodnutí: Statické konstruktory se také spustí při vstupu do instančních metod, pokud nebyl statický konstruktor beforefieldinit
, v takovém případě se statické konstruktory spustí před přístupem k prvnímu statickému poli. https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run
Designové schůzky
2017-03-08 poznámky ze schůzky LDM2017-03-21 poznámky ze schůzky LDM2017-03-23 schůzka "Chování CLR pro výchozí metody rozhraní"2017-04-05 poznámky ze schůzky LDM2017-04-11 poznámky ze schůzky LDM2017-04-18 poznámky ze schůzky LDM2017-04-19 poznámky ze schůzky LDM2017-05-17 poznámky ze schůzky LDM2017-05-31 poznámky ze schůzky LDM2017-06-14 poznámky ze schůzky LDM2018-10-17 poznámky ze schůzky LDM2018-11-14 poznámky ze schůzky LDM
C# feature specifications