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


18 interfészek

18.1 Általános

Az interfész egy szerződést határoz meg. Az interfészt megvalósító osztálynak vagy szerkezetnek be kell tartania a szerződését. Egy interfész több alapillesztőtől örökölhet, az osztály vagy a szerkezet pedig több interfészt is implementálhat.

A felületek metódusokat, tulajdonságokat, eseményeket és indexelőket tartalmazhatnak. Maga a felület nem biztosít implementációkat az általa deklarált tagok számára. Az interfész csupán azokat a tagokat határozza meg, amelyeket az interfészt megvalósító osztályok vagy szerkezetek szolgáltatnak.

18.2 Interfész deklarációi

18.2.1 Általános

A interface_declaration egy type_declaration (14.7.§), amely új interfésztípust deklarál.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Az interface_declaration egy választható attribútumkészlet (22. §), amelyet egy választható interface_modifier-készlet (18.2.2. §) követ, majd egy választható részleges módosító (15.2.7. §), ezt követi a kulcsszó interface és az interfészt nevesítő azonosító, majd egy opcionális variant_type_parameter_list specifikáció (18.2.3. §), majd egy választható interface_base specifikáció (18.2.4. §), amelyet egy opcionális type_parameter_constraints_clause specifikáció követ (15.2.5. §), majd egy interface_body (18.3. §), amelyet opcionálisan pontosvessző követi.

Egy interfész nyilatkozat nem szolgáltathat type_parameter_constraints_clauses-t, kivéve, ha egy variant_type_parameter_listis megadott.

Egy felületi deklaráció, amely biztosít egy variant_type_parameter_list-et, generikus felületi deklaráció. Ezenkívül az általános osztálydeklarációkba vagy általános szerkezetdeklarációkba ágyazott interfészek maguk is általános felületi deklarációk, mivel a típusargumentumokat a tárolótípus típusargumentumait kell megadni egy beépített típus létrehozásához (8.4. §).

18.2.2 Interfész módosítók

Egy interface_declaration opcionálisan tartalmazhat felületmódosítók sorozatát:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (23.2. §) csak nem biztonságos kódban érhető el (23. §).

Fordítási időhiba, hogy ugyanaz a módosító többször is megjelenik egy felületi deklarációban.

A new módosító csak az osztályon belül definiált felületeken engedélyezett. Azt határozza meg, hogy a felület elrejti az azonos nevű örökölt elemet, ahogyan az a 15.3.5 szakaszban le van írva.

A public, protected, internalés private módosítók szabályozzák a felület akadálymentességét. Attól függően, hogy milyen környezetben történik az interfész deklarációja, ezen módosítók közül csak néhány engedélyezhető (7.5.2. §). Ha egy részleges típusdeklaráció (§15.2.7) tartalmaz egy hozzáférési specifikációt (a public, protected, internal és private módosítókon keresztül), a §15.2.2 szabályai érvényesek.

18.2.3 Változó típusú paraméterlisták

18.2.3.1 Általános

A variánstípus paraméterlistái csak interfész- és delegálási típusok esetén fordulhatnak elő. A különbség a szokásos típusparaméterlista-khez képest az egyes típusparaméterek opcionális variancia annotációja.

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

Ha a variancia széljegyzete azout, a típusparaméter kovarianciánsnak minősül. Ha a variancia széljegyzete az in, a típusparaméter állítólag contravariant. Ha nincs eltérési széljegyzet, a típusparaméter invariánsnak minősül.

Példa: Az alábbiakban:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X kovariáns, Y kontravariáns és Z invariáns.

záró példa

Ha egy általános interfész több részből áll (15.2.3. §), minden részleges deklarációnak ugyanazt a varianciát kell meghatároznia minden típusparaméterhez.

18.2.3.2 Varianciabiztonság

A variancia-megjegyzések előfordulása egy típus típusparaméter-listájában korlátozza azokat a helyeket, ahol a típusok a típusdeklarációban előfordulhatnak.

A kimenet-biztonságtalan típusú T esetén, ha az alábbiak egyike fennáll:

  • T egy contravariant típusú paraméter
  • T egy olyan tömbtípus, amely kimenet szempontjából nem biztonságos elemtípussal rendelkezik
  • T egy interfész vagy delegáti típus, amely egy általános típusból Sᵢ,... Aₑ lett létrehozvaS<Xᵢ, ... Xₑ>, ahol az alábbi lehetőségek közül legalább egy Aᵢ teljesül:
    • Xᵢ kovariáns vagy invariáns, és Aᵢ nem biztonságos a kimenete.
    • Xᵢ contravariant vagy invariáns, és Aᵢ bemeneti-nem biztonságos.

A T típus bemenete nem biztonságos , ha az alábbiak egyike rendelkezik:

  • T kovariant típusú paraméter
  • T olyan tömbtípus, amely bemeneti-nem biztonságos elemtípussal rendelkezik
  • T egy interfész vagy delegáti típus, amely egy általános típusból S<Aᵢ,... Aₑ> lett létrehozvaS<Xᵢ, ... Xₑ>, ahol az alábbi lehetőségek közül legalább egy Aᵢ teljesül:
    • Xᵢ covariáns vagy invariáns, és Aᵢ bemenet szempontjából nem biztonságos.
    • Xᵢ contravariant vagy invariáns, és Aᵢ kimenete nem biztonságos.

Intuitív módon, a kimeneti pozícióban a kimeneti szempontból nem biztonságos típus tilos, és a bemeneti pozícióban a bemeneti szempontból nem biztonságos típus nem megengedett.

A típus kimenetbiztos, ha nem kimenet-nem biztonságos, és bemenet-biztonságos, ha nem bemeneti-nem biztonságos.

18.2.3.3 Variancia-átalakítás

A varianciajegyzetek célja, hogy megengedőbb (de mégis típusbiztos) átalakításokat biztosítson az interfész- és delegációs típusokra. E célból az implicit (10.2. §) és explicit konverziók (10.3. §) definíciói a variancia-konvertálhatóság fogalmát használják, amely a következőképpen van definiálva:

A típus T<Aᵢ, ..., Aᵥ> variancia-átalakítható típussá T<Bᵢ, ..., Bᵥ> , ha T egy interfész vagy egy delegált típus, amelyet a variánstípus paramétereivel deklaráltak T<Xᵢ, ..., Xᵥ>, és minden egyes változattípus-paraméterhez Xᵢ az alábbiak egyike tartozik:

  • Xᵢ kovariáns, és létezik egy implicit hivatkozás vagy identitásátalakítás Aᵢ-ből Bᵢ-be.
  • Xᵢ kontravariáns, és létezik egy implicit hivatkozás vagy identitás konverzió Bᵢ-ről Aᵢ-re.
  • invariáns, és van egy identitásátalakítás a -ból .

18.2.4 Alapfelületek

Az interfészek örökölhetnek nulla vagy több felülettípust, amelyeket az interfész explicit alapfelületeinek neveznek. Ha egy interfész egy vagy több explicit alapinterfésszel rendelkezik, akkor az interfész-azonosítót kettőspont és vesszővel tagolt alapinterfész-típusok listája következik.

interface_base
    : ':' interface_type_list
    ;

Az explicit alapinterfészek felépíthetők interfésztípusok (§8.4, §18.2). Az alapfelület önmagában nem lehet típusparaméter, de magában foglalhatja a hatókörben lévő típusparamétereket is.

A létrehozott interfésztípusok esetében az explicit alapinterfészek úgy jönnek létre, hogy az általános típusdeklarációkon szereplő explicit alapinterfész deklarációit veszik alapul, és az alapinterfész-deklarációban szereplő type_parameter helyére a létrehozott típus megfelelő type_argument-ja kerül.

Az interfész explicit alapillesztőinek legalább olyan akadálymentesnek kell lenniük, mint maga az interfész (7.5.5. §).

Megjegyzés: Például fordítási hibát okoz, ha egy private vagy internal interfészt ad meg egy interface_base elemhez egy public interfészben. végjegyzet

Fordítási idejű hiba, ha egy interfész közvetlenül vagy közvetve örökli saját magától.

Az interfészek alapfelületei az explicit alapfelületek és azok alapfelületei. Más szóval, az alapinterfészek készlete az explicit alapinterfészek, azok explicit alapinterfészeinek teljes tranzitív lezárása és így tovább. Az interfész örökli az alapfelületek összes tagját.

Példa: Az alábbi kódban

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

Az alapfelületek a IComboBox, IControl, ITextBox és IListBox. Más szóval, a IComboBox fenti felület örökli a tagokat SetText és SetItems, valamint Paint is.

záró példa

A létrehozott általános típusból öröklő tagok a típushelyettesítés után öröklődnek. Ez azt jelzi, hogy a tag összes összetevőtípusa az alaposztály-deklaráció típusparamétereit a class_base specifikációban használt megfelelő típusargumentumokra cseréli.

Példa: Az alábbi kódban

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

a felület IDerived a típusparaméter Combine lecserélése után örökli a T metóduststring[,].

záró példa

Az interfészt megvalósító osztály vagy szerkezet implicit módon implementálja az interfész összes alapillesztőjét is.

A részleges interfész deklaráció több részén található interfészek kezeléséről (15.2.7. §) részletesen a 15.2.4.3. fejezet tárgyalja.

Az illesztőfelület minden alapillesztőfelületének kimenetbiztosnak kell lennie (18.2.3.2. §).

18.3 Interfész törzse

Az interfész interface_body határozza meg a felület tagjait.

interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 Felülettagok

18.4.1 Általános

Az interfészek tagjai az alapfelületektől örökölt tagok, és a felület által deklarált tagok.

interface_member_declaration
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

A felületi deklaráció nulla vagy több tagot deklarál. Az interfész tagjai módszerek, tulajdonságok, események vagy indexelők. Az interfészek nem tartalmazhatnak állandókat, mezőket, operátorokat, példánykonstruktorokat, véglegesítőket vagy típusokat, és nem tartalmazhatnak semmilyen statikus tagot.

Minden felületi tag implicit módon nyilvános hozzáféréssel rendelkezik. Fordítás közbeni hiba, ha az interfésztag-deklarációk tartalmaznak bármilyen módosítót.

Egy interface_declaration létrehoz egy új deklarációs területet (§7.3), és a típusparaméterek, valamint a interface_member_declaration-ok, amelyeket az interface_declaration közvetlen tartalmaz, új tagokat vezetnek be ebbe a deklarációs területbe. A következő szabályok vonatkoznak az interface_member_declaration-ekre:

  • Az interfész-deklaráció variant_type_parameter_list egy típusparaméter neve eltér az ugyanazon variant_type_parameter_list található összes többi típusparaméter nevétől, és különbözik az interfész minden tagjának nevétől.
  • A módszer neve eltér az azonos felületen deklarált összes tulajdonság és esemény nevétől. Ezenkívül egy módszer aláírásának különböznie kell az ugyanazon felületen deklarált összes többi módszer aláírásától, és az ugyanazon felületen deklarált két módszer nem rendelkezhet kizárólag , in, és out különböző aláírással.
  • Egy tulajdonság vagy esemény neve eltér az azonos felületen bejelentett többi tag nevétől.
  • Az indexelő aláírásának különböznie kell az azonos felületen bejelentett összes többi indexelő aláírásától.

Az interfész öröklött tagjai kifejezetten nem részei az interfész deklarációs terének. Így a felület lehetővé teszi, hogy deklaráljon egy olyan tagot, amelynek neve vagy aláírása megegyezik az örökölt tag nevével vagy aláírásával. Ha ez történik, a származtatott illesztőtag azt mondja, hogy elrejtse az alap illesztőtagot. Örökölt tag elrejtése nem tekinthető hibának, azonban a fordító figyelmeztetést ad ki emiatt. A figyelmeztetés mellőzése érdekében a származtatott interfész tag nyilatkozatának tartalmaznia kell egy new módosítót, amely jelzi, hogy a származtatott tag célja az alaptag elrejtése. Ezt a témakört a 7.7.2.3 részletesebben megtárgyalásra kerül.

Ha egy new módosító szerepel egy olyan deklarációban, amely nem rejt el öröklött tagot, a rendszer ennek megfelelően figyelmeztetést ad ki. Ezt a figyelmeztetést a módosító eltávolításával tiltja le a new rendszer.

Megjegyzés: Az osztály object tagjai szigorúan nem tagjai semmilyen felületnek (18.4. §). Az osztály object tagjai azonban bármely felülettípusban elérhetőek a tagok keresésével (12.5. §). végjegyzet

A többrészesen deklarált felület tagjainak halmaza (15.2.7. §) az egyes részekben deklarált tagok egysége. Az interfész-nyilatkozat minden részének szervei azonos deklarációs térrel (7.3. §) osztoznak, és az egyes tagok hatóköre (7.7. §) az összes rész testületére kiterjed.

18.4.2 Interfész metódusok

A felületi metódusok deklarálása interface_method_declarationhasználatával történik:

interface_method_declaration
    : attributes? 'new'? return_type interface_method_header
    | attributes? 'new'? ref_kind ref_return_type interface_method_header
    ;

interface_method_header
    : identifier '(' parameter_list? ')' ';'
    | identifier type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause* ';'
    ;

Az interfészmetódus-deklarációk attribútumai, return_type, ref_return_type, azonosítója és parameter_list jelentése megegyezik az osztályban lévő metódusdeklarációkéval (15.6. §). Az interfészmetódus-deklaráció nem adhat meg metódustörzset, ezért a deklaráció mindig pontosvesszővel végződik.

Az interfészmetódus minden paramétertípusának bemenetbiztosnak kell lennie (18.2.3.2. §), és a visszatérési típusnak vagy void kimenetbiztosnak kell lennie. Ezenkívül minden kimeneti vagy referenciaparaméter-típusnak kimenetbiztosnak kell lennie.

Megjegyzés: A kimeneti paramétereknek bemenetbiztosnak kell lenniük a gyakori megvalósítási korlátozások miatt. végjegyzet

Ezenkívül minden osztálytípus-korlátozásnak, interfésztípus-korlátozásnak és típusparaméter-kényszernek a módszer bármely típusparaméterére vonatkozóan bemenetbiztosnak kell lennie.

Ezenkívül minden osztálytípus-korlátozásnak, interfésztípus-megkötésnek és típusparaméter-kényszernek a módszer bármely típusparaméterén bemenetbiztosnak kell lennie.

Ezek a szabályok biztosítják, hogy az interfész kovariáns vagy kontravariáns használata típusbiztos maradjon.

Példa:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

A T típusparaméter-korlátozás a U-en történő használata hibás, mert nem biztonságos bemenetként.

Ha ez a korlátozás nincs érvényben, a típusbiztonságot a következő módon lehet megsérteni:

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

I<B> b = new C();
b.M<E>();

Ez valójában egy hívás C.M<E>-ra. De ez a hívás megköveteli, hogy a E a D-ből származzon, így a típusbiztonság sérülne itt.

záró példa

18.4.3 Interfész tulajdonságai

Az illesztőtulajdonságokat interface_property_declaration-okkal deklarálják.

interface_property_declaration
    : attributes? 'new'? type identifier '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
    ;

interface_accessors
    : attributes? 'get' ';'
    | attributes? 'set' ';'
    | attributes? 'get' ';' attributes? 'set' ';'
    | attributes? 'set' ';' attributes? 'get' ';'
    ;

ref_interface_accessor
    : attributes? 'get' ';'
    ;

Az interfésztulajdonság-deklaráció attribútumainak, típusának és azonosítójának jelentése megegyezik egy osztály tulajdonságdeklarációjának jelentésével (15.7. §).

Az interfésztulajdonság-deklaráció tartozékai megfelelnek az osztálytulajdonság-deklaráció tartozékainak (15.7.3. §), azzal a kivételével, hogy a accessor_body mindig pontosvesszővel kell rendelkezniük. Így a hozzáférők egyszerűen jelzik, hogy a tulajdonság olvasható és írható, csak olvasható, vagy csak írható.

Az illesztőtulajdonság típusának kimenetbiztosnak kell lennie, ha van beszerelhető tartozék, és bemenetbiztosnak kell lennie, ha van beállítva tartozék.

18.4.4 Interfészesemények

Az interfészesemények az interface_event_declaration deklarálásával vannak megadva.

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

Az interfészesemény-deklaráció attribútumai, típusa és azonosítója megegyezik az osztály eseménydeklarációinak jelentésével (15.8. §).

Az interfészesemény típusának bemenetbiztosnak kell lennie.

18.4.5 Felületindexelők

A felületindexelők interface_indexer_declarationekkel vannak deklarálva:

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

Az illesztőindex-deklaráció attribútumai, típusa és parameter_list ugyanazzal a jelentéssel rendelkeznek, mint egy indexelő deklarációé egy osztályban (15.9. §).

Az interfész indexelő deklaráció hozzáférői megfelelnek az osztály indexelő deklarációjának hozzáférőinek (15.9. §), azzal a kitétellel, hogy a accessor_body mindig pontosvessző legyen. Így az elérési metódusok egyszerűen jelzik, hogy az indexelő olvasható és írható, csak olvasható, vagy csak írható.

Az illesztőindexelő minden paramétertípusának bemenetbiztosnak kell lennie (18.2.3.2. §). Ezenkívül minden kimeneti vagy referenciaparaméter-típusnak kimenetbiztosnak kell lennie.

Megjegyzés: A kimeneti paramétereknek bemenetbiztosnak kell lenniük a gyakori megvalósítási korlátozások miatt. végjegyzet

Az interface indexelő típusának kimenetbiztosnak kell lennie, ha van get hozzáférő, és bemenetbiztosnak kell lennie, ha van set hozzáférő.

18.4.6 Felülettag-hozzáférés

A felülettagok hozzáférése (§12.8.7) és indexelő hozzáféréssel (§12.8.12.3) az űrlap I.M és I[A]kifejezésével érhető el, ahol a I felülettípus, M az adott felülettípus metódusa, tulajdonsága vagy eseménye, A pedig indexelő argumentumlista.

A szigorúan egyszeres öröklésű felületek esetén (az öröklési lánc minden felülete pontosan nulla vagy egy közvetlen alapfelülettel rendelkezik), a tagkeresés (§12.5), a metódushívás (§12.8.10.2) és az indexelő hozzáférés (§12.8.12.3) szabályai pontosan megegyeznek az osztályok és a struktúrák esetében: A jobban származtatott tagok elrejtik a kevésbé származtatott tagokat ugyanazzal a névvel vagy aláírással. A többöröklésű felületek esetében azonban kétértelműség fordulhat elő, ha két vagy több nem kapcsolódó alapfelület azonos nevű vagy aláírású tagokat deklarál. Ez az alcím számos példát mutat be, amelyek közül néhány kétértelműséghez vezet, mások pedig nem. A kétértelműségek megoldásához minden esetben explicit szereplők használhatók.

Példa: Az alábbi kódban

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

az első utasítás fordítási időbeli hibát okoz, mert a tag keresése Count-ben (IListCounter) nem egyértelmű. Ahogy a példában látható, a kétértelműséget az x megfelelő alapinterfész típusra történő átalakításával lehet megoldani. Az ilyen kasztoknak nincs futásidejű költségük – csupán abból állnak, hogy fordításkor a példányt kevésbé specifikus típusként kezelik.

záró példa

Példa: Az alábbi kódban

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

a n.Add(1) meghívás a túlterhelés feloldási szabályainak alkalmazásával választja ki a IInteger.Add a §12.6.4 szerint. Hasonlóképpen, a hívás n.Add(1.0) kiválasztja IDouble.Add. Explicit leadások beszúrásakor csak egy jelöltmetódus van, így nincs kétértelműség.

záró példa

Példa: Az alábbi kódban

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

a IBase.F tag el van rejtve a ILeft.F tag által. A d.F(1) hívása így ILeft.F-t választja, bár IBase.F úgy tűnik, hogy nincs elrejtve az IRight-en keresztül vezető elérési úton.

A többöröklésű felületeken való elrejtés intuitív szabálya egyszerűen a következő: Ha egy tag bármely elérési útvonalon el van rejtve, az minden elérési útvonalon el van rejtve. Mivel a hozzáférési útvonal IDerived és ILeft között elrejti IBase-t, a tag szintén el van rejtve a hozzáférési útvonalban IBase.F és IDerived között.

záró példa

18.5 A felület minősített tagnevei

Az illesztőtagot néha minősített illesztőtag néven emlegetik. Az interfész tag minősített neve azon interfész neve, amelyben a tag deklarálva van, ezt egy pont követi, majd a tag neve. A tag minősített neve arra a felületre hivatkozik, amelyben a tag deklarálva van.

Példa: Adott deklarációk esetén

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

A(z) Paint minősített neve IControl.Paint, és a(z) SetText minősített neve ITextBox.SetText. A fenti példában nem lehet hivatkozni a következőre PaintITextBox.Paint: .

záró példa

Ha egy felület egy névtér része, a minősített illesztőtagok neve magában foglalhatja a névtér nevét.

Példa:

namespace System
{
    public interface ICloneable
    {
        object Clone();
    }
}

System névtéren belül mind a ICloneable.Clone, mind a System.ICloneable.Clone a Clone metódushoz tartozó interfész tagnevek.

záró példa

18.6 Interfész implementációi

18.6.1 Általános

A felületeket osztályok és szerkezetek implementálhatják. Annak jelzéséhez, hogy egy osztály vagy strustruktúra közvetlenül implementál egy interfészt, az interfész szerepel az osztály vagy a szerkezet alaposztálylistájában.

Példa:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

záró példa

Az interfészt közvetlenül megvalósító osztály vagy struktúra implicit módon implementálja az interfész összes alapillesztőjét is. Ez akkor is igaz, ha az osztály vagy a szerkezet nem sorolja fel explicit módon az alaposztálylistában szereplő összes alapillesztőt.

Példa:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Itt a TextBox osztály mind a IControl, mind a ITextBox-t megvalósítja.

záró példa

Amikor egy osztály C közvetlenül implementál egy felületet, az összes származtatott C osztály implicit módon is implementálja az interfészt.

Az osztálydeklarációban megadott alapillesztők lehetnek felépített illesztőtípusok (§8.4, §18.2).

Példa: Az alábbi kód bemutatja, hogyan valósíthat meg egy osztály létrehozott felülettípusokat:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

záró példa

Az általános osztály deklaráció alapfelületeinek meg kell felelniük az §18.6.3-ban leírt egyediség szabályának.

18.6.2 Az interfész tagok explicit implementációi

Interfészek megvalósítása céljából egy osztály vagy strustruktúra explicit felülettag-implementációkat deklarálhat. Az explicit felülettag-implementáció egy metódus, tulajdonság, esemény vagy indexelő deklaráció, amely egy minősített felülettag nevére hivatkozik.

Példa:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

Itt található a IDictionary<int,T>.this és IDictionary<int,T>.Add explicit felülettag-implementációk.

záró példa

Példa: Bizonyos esetekben előfordulhat, hogy az illesztőtag neve nem felel meg a implementálási osztálynak, ebben az esetben a felülettag explicit felülettag-implementációval implementálható. Egy fájl absztrakciót megvalósító osztály például valószínűleg implementál egy Close tagfüggvényt, amely felszabadítja a fájlerőforrást, és explicit interfész tagfüggvény implementációval valósítja meg az Dispose metódusát az IDisposable interfésznek.

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

záró példa

Az explicit felülettag-implementáció nem érhető el a megfelelő illesztőtag nevével egy metódushívásban, tulajdonsághozzáférésben, eseményhozzáférésben vagy indexelő hozzáférésben. Az explicit felülettag-implementáció csak egy felületpéldányon keresztül érhető el, és ebben az esetben egyszerűen a tag neve hivatkozik rá.

Fordítási idejű hiba, ha egy explicit felülettag implementációja bármilyen módosítót tartalmaz az vagy extern kivételével.

Explicit interfész metódus implementációjának fordítási idejű hibája, ha type_parameter_constraints_clause elemeket tartalmaz. Az általános explicit felületi metódus implementálásának korlátait a rendszer a felületmetódustól örökli.

Megjegyzés: Az explicit interfész tagok implementációi más hozzáférhetőségi jellemzőkkel rendelkeznek, mint a többi tag. Mivel az explicit felülettag-implementációk soha nem érhetők el egy metódushívásban vagy tulajdonsághozzáférésben lévő minősített illesztőtagnéven keresztül, bizonyos értelemben privátak. Mivel azonban az interfészen keresztül is elérhetők, olyan értelemben is nyilvánosak, mint az a felület, amelyben deklarálva vannak. Az explicit felülettag-implementációk két elsődleges célt szolgálnak:

  • Mivel az explicit illesztőtag-implementációk nem érhetők el osztály- vagy struktúrapéldányokon keresztül, lehetővé teszik, hogy a felületi implementációk ki legyenek zárva egy osztály vagy struktúra nyilvános felületéről. Ez különösen akkor hasznos, ha egy osztály vagy strustruktúra olyan belső felületet implementál, amely nem érdekli az adott osztály vagy szerkezet fogyasztóját.
  • Az explicit felülettag-implementációk lehetővé teszik az azonos aláírással rendelkező felülettagok egyértelműsítését. Explicit felülettag-implementációk nélkül lehetetlen lenne, hogy egy osztály vagy strustruktúra azonos aláírással és visszatérési típussal rendelkezzen az illesztőtagok különböző implementációival, mivel egy osztály vagy strustruktúra esetében lehetetlen lenne bármilyen implementációt végrehajtani az azonos aláírású, de eltérő visszatérési típusok esetén.

végjegyzet

Ahhoz, hogy az explicit felülettag-implementáció érvényes legyen, az osztálynak vagy a szerkezetnek el kell neveznie egy interfészt az alaposztálylistájában, amely egy olyan tagot tartalmaz, akinek a megfelelő felülettag neve, típusa, típusparamétereinek száma és paramétertípusai pontosan egyeznek az explicit felülettag-implementációéval. Ha egy interfészfüggvény-tag rendelkezik paramétertömbbel, a társított explicit felülettag-implementáció megfelelő paramétere engedélyezett, de nem kötelező, hogy rendelkezzen a params módosítóval. Ha az interfészfüggvény-tag nem rendelkezik paramétertömbbel, akkor a társított explicit illesztőtag-implementáció nem rendelkezik paramétertömbbel.

Példa: Például a következő osztályban

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

A IComparable.CompareTo deklarációja fordítási idejű hibát eredményez, mert IComparable nem szerepel a Shape alaposztályainak listájában, és nem alapinterfésze a ICloneable-nak. Hasonlóképpen, a deklarációkban

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

ICloneable.Clone deklarálása Ellipse fordítási hibát eredményez, mert ICloneable nincs kifejezetten felsorolva Ellipse alaposztálylistájában.

záró példa

Az explicit interfész tagjának kvalifikált tagneve arra az interfészre hivatkozik, amelyben a tagot deklarálták.

Példa: Így a deklarációkban

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

A Paint explicit felülettag-implementációját IControl.Paint formájában kell írni, nem ITextBox.Paint.

záró példa

18.6.3 A megvalósított interfészek egyedisége

Az általános típusdeklarációval megvalósított interfészeknek minden lehetséges felépített típus esetében egyedinek kell maradniuk. E szabály nélkül lehetetlen lenne meghatározni a megfelelő metódust bizonyos létrehozott típusok meghívásához.

Példa: Tegyük fel, hogy egy általános osztály deklarációja a következőképpen írható:

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

Ha ez engedélyezve lenne, lehetetlen lenne meghatározni, hogy a következő esetben melyik kódot kell végrehajtani:

I<int> x = new X<int, int>();
x.F();

záró példa

Annak megállapításához, hogy egy általános típusú deklaráció felületlistája érvényes-e, a következő lépéseket kell végrehajtani:

  • Legyen L az általános osztályban, szerkezetben vagy interfészdeklarációban Cközvetlenül megadott interfészek listája.
  • Adja hozzá a L elemekhez a L már meglévő illesztőinek bármely alapillesztőjét.
  • Távolítsa el az ismétlődéseket a L-ból.
  • Ha a típusargumentumok C-be való behelyettesítése után bármely lehetséges konstruált típus két azonos interfészhez vezet L-ben, akkor a L deklarációja érvénytelen. A kényszer deklarációk nem tekinthetők meg az összes lehetséges felépített típus meghatározásakor.

Megjegyzés: A fenti osztálydeklarációban X a felületlista L a következőkből áll l<U> : és I<V>. A deklaráció érvénytelen, mert bármely összefércelés, ahol U és V ugyanaz a típus, ezt a két illesztőfelületet azonos típusokká tenné. végjegyzet

A különböző öröklési szinteken megadott interfészek egyesíthetik az alábbiakat:

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

Ez a kód akkor is érvényes, ha Derived<U,V> mind I<U>, mind I<V> megvalósít. A kód

I<int> x = new Derived<int, int>();
x.F();

Meghívja a metódust Derived, mivel Derived<int,int>' hatékonyan újra implementálja I<int>-t (§18.6.7).

18.6.4 Általános módszerek alkalmazása

Amikor egy általános módszer implicit módon implementál egy interfészmetódust, az egyes metódustípus-paraméterekre vonatkozó korlátozásoknak egyenértékűnek kell lenniük mindkét deklarációban (miután az interfésztípus paramétereit lecserélték a megfelelő típusargumentumokra), ahol a metódustípus paramétereit a balról jobbra lévő sorszámok azonosítják.

Példa: A következő kódban:

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

a módszer C.F<T> implicit módon implementálI<object,C,string>.F<T>. Ebben az esetben nincs szükség (és nem is engedélyezett) a korlátozás C.F<T> megadására, T: object mivel object az minden típusparaméter implicit kényszere. A metódus C.G<T> implicit módon implementál I<object,C,string>.G<T> , mert a kényszerek megegyeznek az illesztőben lévőkkel, miután az illesztőtípus paramétereit lecserélte a megfelelő típusargumentumokra. A metódus C.H<T> kényszere hiba, mert a lezárt típusok (string ebben az esetben) nem használhatók kényszerként. A kényszer kihagyása szintén hiba lenne, mivel az implicit interfészmetódus implementációinak megkötéseinek meg kell egyeznie. Így lehetetlen implicit módon implementálni I<object,C,string>.H<T>. Ez az interfészmetódus csak explicit felülettag-implementációval implementálható:

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

Ebben az esetben az explicit felülettag-implementáció egy olyan nyilvános metódust hív meg, amely szigorúan gyengébb korlátozásokkal rendelkezik. A t-ből s-be való hozzárendelés érvényes, mivel T örökli a kényszert T: string, annak ellenére, hogy ez a kényszer nem fejezhető ki a forráskódban. záró példa

Megjegyzés: Ha egy általános módszer explicit módon implementál egy interfészmetódust, a implementálási módszerre nincs korlátozás (15.7.1. §, 18.6.2. §). végjegyzet

18.6.5 Felületleképezés

Az osztálynak vagy a szerkezetnek biztosítania kell az interfészek minden olyan tagjának implementációját, amely szerepel az osztály vagy a szerkezet alaposztálylistájában. Az interfésztagok implementációinak implementálási folyamata egy implementációs osztályban vagy strustruktúra esetében interfészleképezésnek nevezik.

Egy osztály vagy struktúra C interfész-leképezése megkeresi az implementációt az egyes interfészek minden tagjához, amely az alaposztálylistában van megadva C. Egy adott interfésztag I.M implementálása – ahol I az az interfész, amelyben a tagot M deklarálták – az egyes osztályok vagy struktúrák S felülvizsgálatával van meghatározva, kezdve a C osztállyal vagy struktúrával, és ezt minden egyes egymást követő alaposztály C esetében megismételve, amíg egyezésre nem bukkanunk:

  • Ha a S deklarál egy explicit felülettag-implementációt, amely megfelel a I és M tagoknak, akkor ez a tag a I.M implementációja.
  • Ellenkező esetben, ha a S egy nem statikus nyilvános tag számára tartalmaz deklarációt, amely megfelel a M-nek, akkor ez a tag a I.M megvalósítása. Ha egynél több tag egyezik, nincs meghatározva, hogy melyik tag az implementációja I.M. Ez a helyzet csak akkor fordulhat elő, ha S egy olyan létrehozott típus, amelyben az általános típusban deklarált két tag eltérő aláírással rendelkezik, de a típusargumentumok azonosvá teszik az aláírásukat.

Fordítási időhiba lép fel, ha a implementációk nem találhatók a rendszer alaposztálylistájában megadott összes felület minden tagjára Cvonatkozóan. Az interfész tagjai közé tartoznak azok a tagok, amelyek az alapinterfészekből öröklődnek.

A létrehozott interfésztípus tagjait úgy tekintjük, hogy az egyes típusparamétereket az §15.3.3-ben megadott megfelelő típusargumentumokkal helyettesítik.

Példa: Például az általános felületi deklaráció miatt:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

a létrehozott felület I<string[]> tagjai:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

záró példa

Felületleképezés céljából egy osztály vagy strucctag A akkor egyezik meg egy illesztőtagval B , ha:

  • A és B metódusok, és A és B neve, típusa és paraméterlistája azonos.
  • A és B tulajdonságok, a név és a típus A , és B azonosak, és A ugyanazokkal a tartozékokkal rendelkezik, mint B (A megengedett, hogy további tartozékokkal rendelkezzen, ha nem explicit felületi tag 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 paraméterlistái azonosak, és A ugyanazokkal a hozzáférési metódusokkal rendelkezik, mint B (A további hozzáférési metódusokkal is rendelkezhet, ha nem explicit interfész-tag implementáció).

Az interfész-leképezési algoritmus jelentős következményei a következők:

  • Az explicit felülettag-implementációk elsőbbséget élveznek az ugyanabban az osztályban vagy szerkezetben lévő többi taggal szemben az illesztőtagot megvalósító osztály vagy tagstruktúra meghatározásakor.
  • Sem a nem nyilvános, sem a statikus tagok nem vesznek részt a felületleképezésben.

Példa: Az alábbi kódban

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

A(z) ICloneable.Clone tagja a(z) C-ban a(z) Clone megvalósításává válik a(z) ICloneable-ban, mivel az explicit felülettag-implementációk elsőbbséget élveznek más tagokkal szemben.

záró példa

Ha egy osztály vagy szerkezet két vagy több olyan illesztőt implementál, amely azonos nevű, típusú és paramétertípusú tagot tartalmaz, az illesztőtagok mindegyike megfeleltethető egyetlen osztályra vagy strukturált tagra.

Példa:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

Itt mind a Paint, mind a IControl módszerei a IForm metódushoz lettek megfeleltetve a Paint-ben. Természetesen külön explicit felülettag-implementációk is lehetségesek a két módszerhez.

záró példa

Ha egy osztály vagy strustruktúra rejtett tagokat tartalmazó felületet implementál, akkor előfordulhat, hogy egyes tagokat explicit felületi tag-implementációkkal kell implementálni.

Példa:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

Ennek az interfésznek a megvalósítása legalább egy explicit felületi tag implementálását igényelné, és az alábbi űrlapok egyikét venné igénybe

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

záró példa

Ha egy osztály több, azonos alapfelülettel rendelkező adaptert implementál, az alapillesztőnek csak egy implementációja lehet.

Példa: Az alábbi kódban

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

nem lehet külön implementációkat létrehozni az IControl alaposztálylistában szereplő névhez, az IControl által örökölt ITextBox, és az IControl által örökölt IListBox. Ezen interfészek esetében nincs külön identitás fogalma. Ehelyett a ITextBox és a IListBox ugyanazt a IControl implementációt osztja meg, és a ComboBox egyszerűen úgy tekinthető, mint amely három interfészt valósít meg: IControl, ITextBox, és IListBox.

záró példa

Az alaposztály tagjai részt vesznek a felületleképezésben.

Példa: Az alábbi kódban

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

A F módszert a Class1 a Class2's rendszer implementációjában használják Interface1.

záró példa

18.6.6 Interfész implementálásának öröklése

Az osztály örökli az alaposztályai által biztosított összes felületi implementációt.

Az interfész explicit újra implementálása nélkül a származtatott osztály semmilyen módon nem módosíthatja az alaposztályoktól örökölt interfész-leképezéseket.

Példa: A deklarációkban

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

a Paint metódus a TextBox-ban elrejti a Paint metódust a Control-ban, de nem módosítja a Control.Paint leképezését az IControl.Paint-re, és az osztálypéldányokon és felületpéldányokon keresztüli Paint hívások a következő hatásokkal járnak

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

záró példa

Ha azonban egy interfészmetódus egy osztály virtuális metódusára van leképezve, lehetséges, hogy a származtatott osztályok felülbírálják a virtuális metódust, és módosítják a felület implementációját.

Példa: A fenti deklarációk újraírása a következőre:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

a következő hatások figyelhetők meg

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

záró példa

Mivel az explicit felülettag-implementációk nem deklarálhatók virtuálisan, nem lehet felülbírálni egy explicit felülettag-implementációt. Az explicit felülettag implementációja azonban teljesen érvényes arra, hogy meghívjon egy másik metódust, és ezt a másik metódust lehet virtuálisan deklarálni, hogy a származtatott osztályok felüldefiniálhassák azt.

Példa:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Itt a Control osztályokból származtatott osztályok a IControl.Paint metódus felülbírálásával specializálhatják a PaintControl végrehajtását.

záró példa

18.6.7 Interfész újra implementálása

Az interfész implementálását öröklő osztály újra implementálhatja a felületet úgy, hogy felveszi az alaposztálylistába.

Az interfészek újra implementálása pontosan ugyanazokat a felületleképezési szabályokat követi, mint egy felület kezdeti implementációja. Így az örökölt felületleképezés semmilyen hatással nincs a felület újra implementálására létrehozott felületleképezésre.

Példa: A deklarációkban

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

az a tény, hogy Control-t IControl.Paint-re leképezi Control.IControl.Paint-t, nem befolyásolja MyControl újramegvalósítását, ami IControl.Paint-t MyControl.Paint-re leképez.

záró példa

Az örökölt nyilvános tag-deklarációk és az örökölt explicit felülettag-deklarációk részt vesznek az újra implementált felületek felületleképezési folyamatában.

Példa:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

Itt az implementáció leképezi az interfész metódusait IMethodsDerived az Derived.F, Base.IMethods.G, Derived.IMethods.H és Base.I elemekre.

záró példa

Amikor egy osztály implementál egy interfészt, implicit módon az interfész összes alapfelületét is implementálja. Hasonlóképpen, az interfész újra implementálása implicit módon az interfész összes alapfelületének újra implementálása is.

Példa:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

Itt a IDerived újraimplementálása is újraimplementálja a IBase-t, hozzárendelve a IBase.F-t a D.F-hoz.

záró példa

18.6.8 Absztrakt osztályok és felületek

A nem absztrakciós osztályhoz hasonlóan az absztrakt osztálynak az osztály alaposztálylistájában felsorolt interfészek összes tagjának implementációit kell biztosítania. Az absztrakt osztályok azonban interfész metódusokat képezhetnek le absztrakt metódusokra.

Példa:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

Itt a IMethods megvalósítása a F és G elemeket absztrakt metódusokra képezi le, amelyeket felül kell bírálni azokban a nem absztrakt osztályokban, amelyek a C-ből származnak.

záró példa

Az explicit felülettag-implementációk nem lehetnek absztraktak, de az explicit felülettag-implementációk természetesen absztrakt metódusokat is meghívhatnak.

Példa:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

Itt a C-ből származó nem absztrakt osztályoknak felül kell bírálniuk a FF-t és a GG-t, így biztosítva ezzel a IMethods tényleges megvalósítását.

záró példa