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


15 osztály

15.1 Általános

Az osztály olyan adatstruktúra, amely tartalmazhat adattagokat (állandókat és mezőket), függvénytagokat (metódusokat, tulajdonságokat, eseményeket, indexelőket, operátorokat, példánykonstruktorokat, véglegesítőket és statikus konstruktorokat) és beágyazott típusokat. Az osztálytípusok támogatják az öröklést, egy olyan mechanizmust, amellyel egy származtatott osztály kiterjesztheti és specializálhatja az alaposztályt.

15.2 Osztálydeklarációk

15.2.1 Általános

A class_declaration egy type_declaration (14.7.§), amely új osztályt deklarál.

class_declaration
    : attributes? class_modifier* 'partial'? 'class' identifier
        type_parameter_list? class_base? type_parameter_constraints_clause*
        class_body ';'?
    ;

A class_declaration egy választható attribútumkészletből(22. §) áll, amelyet class_modifierválasztható készlet követ (15.2.2.2. §), majd egy választható partial módosító (15.2.7. §), amelyet a kulcsszó class és az osztály nevét tartalmazó azonosító követ, type_parameter_list(15.2.3. §), amelyet egy opcionális class_base specifikáció követ (15.2.4. §).), majd a type_parameter_constraints_clauseválasztható készlete (15.2.5. §), majd egy class_body (15.2.6. §), amelyet opcionálisan pontosvessző követ.

Az osztálydeklaráció nem nyújt type_parameter_constraints_clause-t, hacsak nem szállít type_parameter_list-et is.

A type_parameter_list ellátó osztálydeklaráció általános osztálydeklaráció. Ezenkívül az általános osztálydeklarációkba vagy általános szerkezet deklarációkba ágyazott osztályok maguk is általános osztálydeklarációk, mivel a tartalmú típus típusargumentumait egy beépített típus létrehozásához kell megadni (8.4. §).

15.2.2 Osztálymódosítók

15.2.2.1 Általános

A class_declaration opcionálisan osztálymódosítók sorozatát is tartalmazhatják:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | 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 osztálydeklarációban.

A new módosító beágyazott osztályokban engedélyezett. Azt határozza meg, hogy az osztály a 15.3.5. Fordítási idő hibája, hogy a new módosító olyan osztálydeklaráción jelenik meg, amely nem beágyazott osztálydeklaráció.

A public, protected, internalés private módosítók szabályozzák az osztály akadálymentességét. Az osztálydeklaráció kontextusától függően előfordulhat, hogy egyes módosítók nem engedélyezettek (7.5.2. §).

Ha a részleges típusdeklaráció (15.2.7. §) tartalmaz egy akadálymentességi specifikációt (a public, protected, és internalprivate módosítókon keresztül), a specifikációnak meg kell egyeznie az akadálymentességi specifikációt tartalmazó többi résztel. Ha a részleges típus egyik része sem tartalmaz akadálymentességi specifikációt, a típus a megfelelő alapértelmezett akadálymentességet kapja (7.5.2.§).

A abstract, sealedés static a módosítókat az alábbi alklámokban tárgyaljuk.

15.2.2.2 Absztrakt osztályok

A abstract módosító azt jelzi, hogy egy osztály hiányos, és hogy csak alaposztályként használható. Az absztrakt osztály az alábbi módokon tér el a nem absztrakt osztálytól:

  • Az absztrakt osztályok nem hozhatók létre közvetlenül, és fordítási idő hibát jelent az new operátor absztrakt osztályon való használata. Bár lehetnek olyan változók és értékek, amelyek fordítási időtípusai absztraktak, az ilyen változók és értékek szükségszerűen null vagy az absztrakt típusokból származtatott, nem absztrakt osztályok példányaira mutató hivatkozásokat tartalmaznak.
  • Az absztrakt osztályok absztrakt tagokat tartalmazhatnak (de nem kötelezőek).
  • Absztrakt osztály nem zárható le.

Ha egy nem absztrakt osztály absztrakt osztályból származik, a nem absztrakt osztálynak tartalmaznia kell az összes örökölt absztrakt tag tényleges implementációit, ezáltal felül kell bírálni ezeket az absztrakt tagokat.

Példa: Az alábbi kódban

abstract class A
{
    public abstract void F();
}

abstract class B : A
{
    public void G() {}
}

class C : B
{
    public override void F()
    {
        // Actual implementation of F
    }
}

az absztrakt osztály A egy absztrakt metódust Fvezet be. Az osztály B egy további módszert Gvezet be, de mivel nem nyújt implementációt, F absztraktnak is kell minősíteniB. Osztály C felülbírálások F , és tényleges megvalósítást biztosít. Mivel nincsenek absztrakt tagok a C, C megengedett (de nem kötelező) nem absztrakt.

záró példa

Ha egy osztály egy részleges típusdeklarációjának egy vagy több része (15.2.7. §) tartalmazza a abstract módosítót, az osztály absztrakt. Ellenkező esetben az osztály nem absztrakt.

15.2.2.3 Lezárt osztályok

A sealed módosító az osztályból való származtatás megelőzésére szolgál. Fordítási időhiba akkor fordul elő, ha egy lezárt osztály egy másik osztály alaposztályaként van megadva.

A lezárt osztály nem lehet absztrakt osztály is.

Megjegyzés: A sealed módosító elsősorban a nem szándékos származtatás megelőzésére szolgál, de bizonyos futásidejű optimalizálásokat is lehetővé tesz. Különösen azért, mert egy lezárt osztályról ismert, hogy soha nem rendelkezik származtatott osztályokkal, lehetséges a lezárt osztálypéldányok virtuális függvénytag-meghívásait nem virtuális hívássá alakítani. végjegyzet

Ha egy osztály egy részleges típusdeklarációjának egy vagy több része (15.2.7. §) tartalmazza a sealed módosítót, az osztály le van zárva. Ellenkező esetben az osztály nincs lezárva.

15.2.2.4 Statikus osztályok

15.2.2.4.1 Általános

A static módosító a deklarált osztály statikus osztályként való megjelölésére szolgál. A statikus osztály nem hozható létre, típusként nem használható, és csak statikus tagokat tartalmazhat. Csak statikus osztály tartalmazhat kiterjesztési módszerek deklarációit (15.6.10. §).

A statikus osztály deklarációira a következő korlátozások vonatkoznak:

  • A statikus osztály nem tartalmazhat sealed módosító vagy abstract módosító elemet. (Mivel azonban egy statikus osztály nem hozható létre vagy származtatható belőle, úgy viselkedik, mintha lezárt és absztrakt lenne.)
  • A statikus osztály nem tartalmazhat class_base specifikációt (15.2.4. §), és nem határozhat meg explicit módon alaposztályt vagy a megvalósított interfészek listáját. A statikus osztály implicit módon örökli a típust object.
  • A statikus osztály csak statikus tagokat tartalmazhat (15.3.8. §).

    Megjegyzés: Minden állandó és beágyazott típus statikus tagként van besorolva. végjegyzet

  • A statikus osztálynak nem lehetnek tagjai protected, private protectedvagy protected internal nem deklarálhatók akadálymentességgel.

Fordítási idő hibát jelent ezen korlátozások bármelyikének megsértése.

A statikus osztályokban nincsenek példánykonstruktorok. Nem deklarálható egy példánykonstruktor statikus osztályban, és a statikus osztályhoz nincs megadva alapértelmezett példánykonstruktor (15.11.5. §).

A statikus osztály tagjai nem automatikusan statikusak, és a tagdeklarációknak explicit módon tartalmazniuk kell egy static módosítót (az állandók és a beágyazott típusok kivételével). Ha egy osztály statikus külső osztályba van ágyazva, a beágyazott osztály nem statikus osztály, kivéve, ha kifejezetten tartalmaz módosítót static .

Ha egy osztály egy részleges típusdeklarációjának egy vagy több része (15.2.7. §) tartalmazza a static módosítót, az osztály statikus. Ellenkező esetben az osztály nem statikus.

15.2.2.4.2 Statikus osztálytípusokra való hivatkozás

A namespace_or_type_name (7.8. §) akkor hivatkozhat statikus osztályra, ha

A primary_expression (12.8. §) akkor hivatkozhat statikus osztályra, ha

  • A primary_expression az E űrlap member_accesstalálható.

Bármely más környezetben fordítási idő hiba statikus osztályra hivatkozni.

Megjegyzés: Például hiba, ha egy statikus osztályt alaposztályként, tagösszetevő-típusként (15.3.7. §), általános típusargumentumként vagy típusparaméter-kényszerként használnak. Hasonlóképpen, a statikus osztály nem használható tömbtípusban, új kifejezésben, öntött kifejezésben, kifejezésként, kifejezésként, sizeof kifejezésként vagy alapértelmezett értékkifejezésben. végjegyzet

15.2.3 Típusparaméterek

A típusparaméter egy egyszerű azonosító, amely egy olyan típusargumentum helyőrzőit jelöli, amelyet egy létrehozott típus létrehozásához ad meg. A constrast szerint a típusargumentum (8.4.2. §) az a típus, amely a típusparaméter helyébe lép a létrehozott típus létrehozásakor.

type_parameter_list
    : '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
    ;

decorated_type_parameter
    : attributes? type_parameter
    ;

type_parameter a 8.5.

Az osztálydeklaráció minden típusparamétere meghatároz egy nevet az osztály deklarációs területén (7.3. §). Így nem lehet ugyanaz a név, mint az adott osztály vagy az osztályban deklarált tag másik típusparamétere. A típusparaméterek neve nem lehet ugyanaz, mint maga a típus.

Két részleges általános típusdeklaráció (ugyanabban a programban) ugyanahhoz a kötetlen általános típushoz járul hozzá, ha a típusparaméterek számának generic_dimension_specifier (12.8.18. §) is ugyanazzal a teljes névvel rendelkezik (7.8.3. §). Két ilyen részleges típusdeklarációnak minden típusparaméterhez ugyanazt a nevet kell megadnia, sorrendben.

15.2.4 Osztály alapspecifikációja

15.2.4.1 Általános

Az osztálydeklaráció tartalmazhat egy class_base specifikációt, amely meghatározza az osztály közvetlen alaposztályát és az osztály által közvetlenül implementált interfészeket (18. §).

class_base
    : ':' class_type
    | ':' interface_type_list
    | ':' class_type ',' interface_type_list
    ;

interface_type_list
    : interface_type (',' interface_type)*
    ;

15.2.4.2 Alaposztályok

Ha egy class_type szerepel a class_base, a deklarált osztály közvetlen alaposztályát adja meg. Ha egy nem részleges osztálydeklaráció nem rendelkezik class_base, vagy ha a class_base csak interfésztípusokat listáz, akkor a közvetlen alaposztályt feltételezzük object. Ha a részleges osztály deklarációja alaposztály-specifikációt tartalmaz, az alaposztály-specifikációnak ugyanarra a típusra kell hivatkozni, mint a részleges típus minden olyan része, amely tartalmazza az alaposztály-specifikációt. Ha egy részleges osztály egyik része sem tartalmaz alaposztály-specifikációt, akkor az alaposztály a .object Az osztály a tagokat a 15.3.4.

Példa: Az alábbi kódban

class A {}
class B : A {}

Az osztály A állítólag a közvetlen alaposztálya B, és B azt mondják , hogy abból származik A. Mivel A explicit módon nem határoz meg közvetlen alaposztályt, a közvetlen alaposztály implicit módon objectvan megadva.

záró példa

Az általános típusú deklarációban (15.3.9.7. §) deklarált beágyazott osztálytípus esetében, ha az általános osztály deklarációjában alaposztály van megadva, a létrehozott típus alaposztályát az alaposztály-deklarációban szereplő type_parameter a létrehozott típus megfelelő type_argument helyettesítésével nyeri ki.

Példa: Az általános osztály deklarációi

class B<U,V> {...}
class G<T> : B<string,T[]> {...}

a létrehozott típus G<int> alaposztálya a következő lenne B<string,int[]>: .

záró példa

Az osztálydeklarációban megadott alaposztály lehet egy konstruktált osztálytípus (8.4. §). Az alaposztály önmagában nem lehet típusparaméter (8.5. §), de magában foglalhatja a hatókörbe tartozó típusparamétereket.

Példa:

class Base<T> {}

// Valid, non-constructed class with constructed base class
class Extend1 : Base<int> {}

// Error, type parameter used as base class
class Extend2<V> : V {}

// Valid, type parameter used as type argument for base class
class Extend3<V> : Base<V> {}

záró példa

Az osztálytípus közvetlen alaposztályának legalább olyan hozzáférhetőnek kell lennie, mint maga az osztálytípus (7.5.5. §). Ez például egy olyan fordítási időhiba, amely miatt egy nyilvános osztály privát vagy belső osztályból származik.

Az osztálytípus közvetlen alaposztálya nem lehet a következő típusok egyike sem: System.Array, System.Delegate, , System.Enumvagy System.ValueType a dynamic típus. Továbbá az általános osztálybevallás nem használható System.Attribute közvetlen vagy közvetett alaposztályként (22.2.1. §).

Egy osztály Aközvetlen alaposztály-specifikációjának jelentésének meghatározásakor a rendszer ideiglenesen feltételeziB, hogy az alaposztály-specifikáció Bobject jelentése önmagától függ.

Példa: A következő

class X<T>
{
    public class Y{}
}

class Z : X<Z.Y> {}

hiba lépett fel, mivel az alaposztály-specifikációban X<Z.Y> a közvetlen alaposztályt Z tekintik objectannak , ezért (a 7.8. § szabályai szerint) Z nem tekinthető tagnak Y.

záró példa

Az osztály alaposztályai a közvetlen alaposztály és annak alaposztályai. Más szóval az alaposztályok halmaza a közvetlen alaposztály-kapcsolat tranzitív lezárása.

Példa: Az alábbiakban:

class A {...}
class B<T> : A {...}
class C<T> : B<IComparable<T>> {...}
class D<T> : C<T[]> {...}

az alaposztályok a D<int> következők: , C<int[]>és B<IComparable<int[]>>Aobject.

záró példa

Az osztály objectkivételével minden osztálynak pontosan egy közvetlen alaposztálya van. Az object osztály nem rendelkezik közvetlen alaposztályokkal, és az összes többi osztály végső alaposztálya.

Egy osztály fordítási idő hibája, amely önmagától függ. Ennek a szabálynak a alkalmazásában az osztály közvetlenül a közvetlen alaposztályától függ (ha van ilyen), és közvetlenül attól a legközelebbi beágyazott osztálytól függ, amelyben beágyazva van (ha van ilyen). A definíció alapján az osztályok teljes készlete, amelytől egy osztály függ, a közvetlen lezárás a kapcsolattól függ.

Példa: A példa

class A : A {}

hibás, mert az osztály önmagától függ. Hasonlóképpen, a példa

class A : B {}
class B : C {}
class C : A {}

hiba lépett fel, mert az osztályok körkörösen függenek egymástól. Végül a példa

class A : B.C {}
class B : A
{
    public class C {}
}

fordítási idő hibát eredményez, mert az A B.C függ (közvetlen alaposztálya), amely függ B (az azonnal belefoglaló osztálytól), amely körkörösen függ.A

záró példa

Az osztály nem függ a benne beágyazott osztályoktól.

Példa: Az alábbi kódban

class A
{
    class B : A {}
}

B A függ (mivel A mind a közvetlen alaposztálya, mind a közvetlenül magában foglaló osztálya), de A nem függ B (mivel B nem alaposztály, és nem is tartalmazza az Aosztályt). Így a példa érvényes.

záró példa

Lezárt osztályból nem lehet származni.

Példa: Az alábbi kódban

sealed class A {}
class B : A {} // Error, cannot derive from a sealed class

Az osztály B hibás, mert a lezárt osztályból Apróbál származni.

záró példa

15.2.4.3 Interfész implementációi

A class_base specifikációk tartalmazhatják az interfésztípusok listáját, ebben az esetben az osztály állítólag implementálja az adott interfésztípusokat. Egy konfigurált osztálytípus esetében, beleértve az általános típusdeklaráción belül deklarált beágyazott típust (15.3.9.7. §) minden implementált illesztőtípust az adott interfész minden type_parameterhelyettesítve a létrehozott típus megfelelő type_argument.

A több részből (15.2.7. §) deklarált típus illesztőinek készlete az egyes részeken megadott interfészek egyesítését képezi. Egy adott felület csak egyszer nevezhető el minden egyes részen, de több rész is nevezheti el ugyanazt az alapillesztőt. Az adott interfész minden egyes tagjának csak egy megvalósítása lehet.

Példa: Az alábbiakban:

partial class C : IA, IB {...}
partial class C : IC {...}
partial class C : IA, IB {...}

az osztály C alapillesztőinek halmaza az IA, IBés IC.

záró példa

Általában minden rész biztosítja az adott részben deklarált interfészek implementálását; ez azonban nem követelmény. Egy rész egy másik részen deklarált felület implementációját is biztosíthatja.

Példa:

partial class X
{
    int IComparable.CompareTo(object o) {...}
}

partial class X : IComparable
{
    ...
}

záró példa

Az osztálydeklarációban megadott alapillesztők felépíthetők illesztőtí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.

Példa: Az alábbi kód bemutatja, hogy egy osztály hogyan implementálhatja és bővítheti a létrehozott tí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

A felület implementációit a 18.6.

15.2.5 Típusparaméter-megkötések

Az általános típus- és metódusdeklarációk opcionálisan megadhatnak típusparaméter-korlátozásokat type_parameter_constraints_clausehozzáadásával.

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
    | secondary_constraints (',' constructor_constraint)?
    | constructor_constraint
    ;

primary_constraint
    : class_type nullable_type_annotation?
    | 'class' nullable_type_annotation?
    | 'struct'
    | 'notnull'
    | 'unmanaged'
    ;

secondary_constraint
    : interface_type nullable_type_annotation?
    | type_parameter nullable_type_annotation?
    ;

secondary_constraints
    : secondary_constraint (',' secondary_constraint)*
    ;

constructor_constraint
    : 'new' '(' ')'
    ;

Minden type_parameter_constraints_clause a jogkivonatból where, majd egy típusparaméter nevéből, majd egy kettőspontból és az adott típusparaméterre vonatkozó korlátozások listájából áll. Minden típusparaméterhez legfeljebb egy where záradék tartozik, és a where záradékok bármilyen sorrendben felsorolhatók. get set A tulajdonság-tartozékban lévő jogkivonatokhoz hasonlóan a where jogkivonat nem kulcsszó.

A záradékban where megadott kényszerek listája a következő összetevők bármelyikét tartalmazhatja ebben a sorrendben: egyetlen elsődleges kényszer, egy vagy több másodlagos kényszer és a konstruktori kényszer. new()

Az elsődleges kényszer lehet egy osztálytípus, a Az osztálytípus és a hivatkozástípus-korlátozás tartalmazhatja a nullable_type_annotation.

A másodlagos kényszer lehet interface_type vagy type_parameter, amelyet opcionálisan egy nullable_type_annotation követ. A nullable_type_annotation jelenléte azt jelzi, hogy a típusargumentum lehet olyan null értékű hivatkozástípus, amely megfelel a kényszernek és egybeesik egy nem null értékű hivatkozástípussal.

A referenciatípus-korlátozás azt határozza meg, hogy a típusparaméterhez használt típusargumentumnak referenciatípusnak kell lennie. Ennek a kényszernek minden olyan osztálytípus, felülettípus, delegálási típus, tömbtípus és típusparaméter megfelel, amely ismerten referenciatípus (az alábbiak szerint).

Az osztálytípus, a hivatkozástípus-korlátozás és a másodlagos kényszerek tartalmazhatják a null értékű típusjegyzetet. A típusparaméteren található megjegyzés jelenléte vagy hiánya a típusargumentum nullabilitási elvárásait jelzi:

  • Ha a kényszer nem tartalmazza a null értékű típusú széljegyzetet, a típusargumentum várhatóan nem null értékű hivatkozástípus lesz. A fordító figyelmeztetést adhat ki, ha a típusargumentum null értékű hivatkozástípus.
  • Ha a kényszer tartalmazza a null értékű típusú széljegyzetet, a kényszert egy nem null értékű hivatkozástípus és egy null értékű hivatkozástípus is kielégíti.

A típusargumentum nullhatóságának nem kell megegyeznie a típusparaméter nullíthatóságával. A fordító figyelmeztetést adhat ki, ha a típusparaméter nullsága nem egyezik meg a típusargumentum nullképességével.

Megjegyzés: Ha meg szeretné adni, hogy egy típusargumentum null értékű hivatkozástípus, ne adja hozzá a null értékű típusú széljegyzetet kényszerként (használja T : class vagy T : BaseClass), hanem használja T? az általános deklarációban a megfelelő null értékű hivatkozástípust a típusargumentumhoz. végjegyzet

A null értékű típusú széljegyzet ?nem használható korlátozás nélküli típusargumentumon.

Ha egy típusparaméter T null értékű hivatkozástípusC?, akkor a rendszer a példányokat nem T?a következőképpen értelmeziC?C??.

Példa: Az alábbi példák azt mutatják be, hogy egy típusargumentum nullhatósága hogyan befolyásolja a típusparaméter deklarációjának nullságát:

public class C
{
}

public static class  Extensions
{
    public static void M<T>(this T? arg) where T : notnull
    {

    }
}

public class Test
{
    public void M()
    {
        C? mightBeNull = new C();
        C notNull = new C();

        int number = 5;
        int? missing = null;

        mightBeNull.M(); // arg is C?
        notNull.M(); //  arg is C?
        number.M(); // arg is int?
        missing.M(); // arg is int?
    }
}

Ha a típusargumentum nem null értékű típus, a ? típusjegyzet azt jelzi, hogy a paraméter a megfelelő null értékű típus. Ha a típusargumentum már null értékű hivatkozástípus, a paraméter ugyanaz a null értékű típus.

záró példa

A nem null kényszer azt határozza meg, hogy a típusparaméterhez használt típusargumentumnak nem null értékű vagy nem null értékű hivatkozástípusnak kell lennie. Olyan típusargumentumok engedélyezettek, amelyek nem nem-null értékű értéktípust vagy nem nem-null értékű hivatkozástípust képviselnek, de a fordító diagnosztikai figyelmeztetést adhat.

Mivel a notnull nem kulcsszó, primary_constraint a nem null kényszer mindig szintaktikailag nem egyértelmű class_type. Kompatibilitási okokból, ha a név egy névkeresése (notnull) sikeres, akkor azt úgy kell tekinteni, mint egy class_type. Ellenkező esetben nem null kényszerként kell kezelni.

Példa: Az alábbi osztály bemutatja, hogy különböző típusú argumentumokat használnak a különböző megkötések ellen, jelezve a fordító által esetleg kiadott figyelmeztetéseket.

#nullable enable
public class C { }
public class A<T> where T : notnull { }
public class B1<T> where T : C { }
public class B2<T> where T : C? { }
class Test
{
    static void M()
    {
        // nonnull constraint allows nonnullable struct type argument
        A<int> x1;
        // possible warning: nonnull constraint prohibits nullable struct type argument
        A<int?> x2;
        // nonnull constraint allows nonnullable class type argument
        A<C> x3;
        // possible warning: nonnull constraint prohibits nullable class type argument
        A<C?> x4;
        // nonnullable base class requirement allows nonnullable class type argument
        B1<C> x5;
        // possible warning: nonnullable base class requirement prohibits nullable class type argument
        B1<C?> x6;
        // nullable base class requirement allows nonnullable class type argument
        B2<C> x7;
        // nullable base class requirement allows nullable class type argument
        B2<C?> x8;
    }
}

Az értéktípus-korlátozás azt határozza meg, hogy a típusparaméterhez használt típusargumentum nem null értékű érték legyen. Az értéktípus-korlátozással rendelkező nem null értékű szerkezettípusok, enumerálástípusok és típusparaméterek megfelelnek ennek a kényszernek. Vegye figyelembe, hogy bár értéktípusként van besorolva, a null értékű értéktípus (8.3.12. §) nem felel meg az értéktípus-korlátozásnak. Az értéktípus-korlátozással rendelkező típusparaméterek nem rendelkeznek a constructor_constraint, bár egy másik, constructor_constraint rendelkező típusparaméter típusargumentumaként is használhatók.

Megjegyzés: A System.Nullable<T> típus a nem null értékű értéktípus kényszerét Tadja meg. Így a formák T?? rekurzívan felépített típusai és Nullable<Nullable<T>> tiltottak. végjegyzet

A nem felügyelt típuskorlátozás azt határozza meg, hogy a típusparaméterhez használt típusargumentum nem null értékű, nem felügyelt típus legyen (8.8. §).

Mivel unmanaged nem kulcsszó, a primary_constraint a nem felügyelt kényszer mindig szintaktikailag nem egyértelmű class_type. Kompatibilitási okokból, ha a név egy névkeresése (12.8.4. §) sikeres, akkor a név unmanaged egy class_type. Ellenkező esetben a rendszer nem felügyelt kényszerként kezeli.

A mutatótípusok soha nem lehetnek típusargumentumok, és nem elégítik ki a típusmegkötéseket, még a nem felügyelt típusok ellenére sem.

Ha a kényszer osztálytípus, illesztőtípus vagy típusparaméter, akkor ez a típus egy minimális "alaptípust" határoz meg, amelyet az adott típusparaméterhez használt összes típusargumentumnak támogatnia kell. Amikor létrehozott típust vagy általános metódust használ, a rendszer a típusargumentumot a típusparaméterre vonatkozó korlátozások alapján ellenőrzi fordításkor. A megadott típusargumentumnak meg kell felelnie a 8.4.5.

A class_type korlátozásnak meg kell felelnie a következő szabályoknak:

  • A típusnak osztálytípusnak kell lennie.
  • A típus nem lehet sealed.
  • A típus nem lehet a következő típusok egyike: System.Array vagy System.ValueType.
  • A típus nem lehet object.
  • Egy adott típusparaméterre legfeljebb egy korlátozás lehet osztálytípus.

A interface_type korlátozásként megadott típusnak meg kell felelnie a következő szabályoknak:

  • A típusnak interfésztípusnak kell lennie.
  • Egy típust csak egyszer lehet megadni egy adott where záradékban.

A kényszer bármelyik esetben magában foglalhatja a társított típus vagy metódus deklarációjának bármely típusparaméterét egy beépített típus részeként, és magában foglalhatja a deklarált típust is.

A típusparaméter-korlátozásként megadott osztály- vagy illesztőtípusnak legalább elérhetőnek kell lennie (7.5.5. §), mint a deklarált általános típus vagy módszer.

A type_parameter korlátozásként megadott típusnak meg kell felelnie a következő szabályoknak:

  • A típus típusparaméternek kell lennie.
  • Egy típust csak egyszer lehet megadni egy adott where záradékban.

Ezenkívül nem lehetnek ciklusok a típusparaméterek függőségi gráfjában, ahol a függőség egy tranzitív reláció, amelyet a következők határoznak meg:

  • Ha típusparamétert T használ a típusparaméter S kényszereként, attólS függ.T
  • Ha egy típusparaméter S egy típusparamétertől T függ, és T egy típusparamétertől U függ, akkor Sattól függU.

Ebben a relációban egy típusparaméter fordítási időhibája önmagától függ (közvetlenül vagy közvetve).

A kényszereknek konzisztensnek kell lenniük a függő típusparaméterek között. Ha a típusparaméter S a típusparamétertől T függ, akkor:

  • T nem rendelkezik értéktípus-korlátozással. Ellenkező esetben gyakorlatilag lezárva van, T így S arra kényszerítik, hogy ugyanaz legyen, mint Ta típus, így nincs szükség két típusparaméterre.
  • Ha S rendelkezik az értéktípusra vonatkozó korlátozással, akkor T nem lehet class_type kényszer.
  • Ha S class_type kényszerrel rendelkezikA
  • Ha S a típusparamétertől U is függ, és U class_type, és A class_typeT, akkor identitáskonvertálást vagy implicit referenciaátalakítást kell létrehozni a típusról a másikra BAtörténő implicit hivatkozás-átalakítássalB.

Érvényes, S hogy az értéktípus-korlátozással rendelkezik, és T a hivatkozástípus-korlátozással rendelkezik. Ez gyakorlatilag korlátozza T a típusokatSystem.Object, System.ValueTypeSystem.Enumés minden felülettípust.

Ha egy where típusparaméter záradéka konstruktori kényszert tartalmaz (amelynek űrlapja new()van), az operátor használatával new létrehozhat példányokat a típusból (12.8.17.2. §). A konstruktori kényszerrel rendelkező típusparaméterekhez használt típusargumentumok értéktípusnak, nem absztrakt, nyilvános paraméter nélküli konstruktorsal rendelkező osztálynak, vagy értéktípus-korlátozással vagy konstruktor-kényszerrel rendelkező típusparaméternek kell lenniük.

Fordítási időhiba vagy constructor_constraint is rendelkezik.

Példa: Az alábbiakban példákat láthat a korlátozásokra:

interface IPrintable
{
    void Print();
}

interface IComparable<T>
{
    int CompareTo(T value);
}

interface IKeyProvider<T>
{
    T GetKey();
}

class Printer<T> where T : IPrintable {...}
class SortedList<T> where T : IComparable<T> {...}

class Dictionary<K,V>
    where K : IComparable<K>
    where V : IPrintable, IKeyProvider<K>, new()
{
    ...
}

Az alábbi példa hibás, mert körkörösséget okoz a típusparaméterek függőségi gráfjában:

class Circular<S,T>
    where S: T
    where T: S // Error, circularity in dependency graph
{
    ...
}

Az alábbi példák további érvénytelen helyzeteket szemléltetnek:

class Sealed<S,T>
    where S : T
    where T : struct // Error, `T` is sealed
{
    ...
}

class A {...}
class B {...}

class Incompat<S,T>
    where S : A, T
    where T : B // Error, incompatible class-type constraints
{
    ...
}

class StructWithClass<S,T,U>
    where S : struct, T
    where T : U
    where U : A // Error, A incompatible with struct
{
    ...
}

záró példa

Egy típusa a következő:

  • Ha C beágyazott típus Outer.Inner , akkor Cₓ beágyazott típus Outerₓ.Innerₓ.
  • Ha CCₓegy típusargumentumokkal G<A¹, ..., Aⁿ> rendelkező, konstruktált típusA¹, ..., Aⁿ, akkor Cₓ a létrehozott típusG<A¹ₓ, ..., Aⁿₓ>.
  • Ha C tömbtípus E[] , akkor Cₓ a tömb típusa Eₓ[].
  • Ha C dinamikus, akkor Cₓ az .object
  • Cₓ Ellenkező esetben az .C

A a következőképpen van definiálva:

Legyünk R olyan típusok, amelyek:

  • A típusparaméter T minden kényszeréhez R tartalmazza a tényleges alaposztályt.
  • A struktúratípus T minden egyes kényszere R tartalmazza a következőtSystem.ValueType: .
  • Az enumerálási típus T minden egyes kényszere R tartalmazza a következőtSystem.Enum: .
  • A delegált típusú T összes korlátozás R tartalmazza a dinamikus törlést.
  • A tömbtípus T minden egyes kényszere R tartalmazza a következőtSystem.Array: .
  • Az osztálytípus T minden egyes kényszere R tartalmazza a dinamikus törlést.

Akkor

  • Ha T értéktípus-korlátozással rendelkezik, akkor a tényleges alaposztálya a System.ValueType.
  • Ellenkező esetben, ha R üres, akkor a tényleges alaposztály a .object
  • Ellenkező esetben a tényleges alaposztály T a készlet legelfoglaltabb típusa (R. §). Ha a készlet nem tartalmaz átfogó típust, akkor a tényleges alaposztály az T .object A konzisztenciaszabályok biztosítják, hogy a legelfoglaltabb típus létezik.

Ha a típusparaméter olyan metódustípus-paraméter, amelynek megkötései öröklődnek az alapmetódustól, a rendszer a típushelyettesítés után számítja ki a tényleges alaposztályt.

Ezek a szabályok biztosítják, hogy a tényleges alaposztály mindig class_type legyen.

A a következőképpen van definiálva:

  • Ha T nincs secondary_constraints, akkor a tényleges interfészkészlete üres.
  • Ha T interface_type megkötések vannak, de nincsenek type_parameter korlátozások, akkor a tényleges illesztőkészlete a interface_type kényszereinek dinamikus törlési készlete.
  • Ha T nincsenek interface_type megkötései, de type_parameter korlátozásokkal rendelkezik, akkor a tényleges illesztőkészlet a type_parameter kényszerek tényleges illesztőkészleteinek egyesítését biztosítja.
  • Ha interface_type és type_parameter korlátozásokkal is rendelkezik, akkor a tényleges illesztőkészlete a interface_typeThalmaza és a type_parameter kényszerek tényleges illesztőkészletei.

A típusparaméter ismerten referenciatípus, ha hivatkozástípus-korlátozással rendelkezik, vagy a tényleges alaposztálya nem vagy objectnemSystem.ValueType. A típusparaméter ismerten nem null értékű hivatkozástípus , ha ismert, hogy hivatkozástípus, és nem null értékű hivatkozástípus-korlátozással rendelkezik.

A korlátozott típusú paramétertípus értékei a kényszerek által sugallt példánytagok elérésére használhatók.

Példa: Az alábbiakban:

interface IPrintable
{
    void Print();
}

class Printer<T> where T : IPrintable
{
    void PrintOne(T x) => x.Print();
}

a metódusok IPrintable közvetlenül x hívhatók meg, mert T az mindig implementálásra IPrintablevan korlátozva.

záró példa

Ha egy részleges általános típusdeklaráció korlátozásokat tartalmaz, a korlátozásoknak meg kell egyeznie az összes többi olyan résztel, amely korlátozásokat is tartalmaz. Pontosabban minden olyan résznek, amely korlátozásokat tartalmaz, ugyanazon típusparaméter-készletre vonatkozó korlátozásokkal kell rendelkeznie, és minden típusparaméter esetében az elsődleges, a másodlagos és a konstruktori kényszerkészletnek egyenértékűnek kell lennie. Két kényszercsoport egyenértékű, ha azonos tagokat tartalmaznak. Ha egy részleges általános típus egyik része sem ad meg típusparaméter-korlátozásokat, a típusparaméterek korlátozás nélkülinek minősülnek.

Példa:

partial class Map<K,V>
    where K : IComparable<K>
    where V : IKeyProvider<K>, new()
{
    ...
}

partial class Map<K,V>
    where V : IKeyProvider<K>, new()
    where K : IComparable<K>
{
    ...
}

partial class Map<K,V>
{
    ...
}

helyes, mert azok a részek, amelyek kényszereket tartalmaznak (az első kettő) hatékonyan ugyanazt az elsődleges, másodlagos és konstruktori kényszerkészletet adják meg ugyanazon típusparaméter-készlethez.

záró példa

15.2.6 Osztály törzse

Az osztály class_body határozza meg az osztály tagjait.

class_body
    : '{' class_member_declaration* '}'
    ;

15.2.7 Részleges típusbevallások

A módosító partial több részből álló osztály-, szerkezet- vagy illesztőtípus definiálásakor használatos. A partial módosító egy környezetfüggő kulcsszó (6.4.4.4. §), és közvetlenül a kulcsszavak classelőtt speciális jelentéssel rendelkezik, structés interface. (A részleges típus tartalmazhat részleges módszerdeklarációkat (15.6.9. §).

A részleges típusdeklaráció minden részének tartalmaznia kell egy partial módosítót, és ugyanabban a névtérben kell deklarálni, vagy olyan típusban kell deklarálni, mint a többi rész. A partial módosító azt jelzi, hogy a típusdeklaráció további részei máshol is létezhetnek, de az ilyen további részek megléte nem követelmény; a módosító csak a típus partial deklarációjára érvényes. Csak egy részleges típusú deklaráció esetén érvényes az alaposztály vagy a implementált interfészek belefoglalása. Az alaposztály vagy a megvalósított interfészek összes deklarációjának azonban egyeznie kell, beleértve a megadott típusargumentumok érvénytelenségét is.

A részleges típus minden részét össze kell állítani úgy, hogy a részek fordításkor egyesíthetők legyenek. A részleges típusok nem teszik lehetővé a már lefordított típusok kiterjesztését.

A beágyazott típusok a módosító használatával partial több részre is deklarálhatók. A rendszer általában a tartalmazó típust is deklarálja partial , és a beágyazott típus egyes részeit a rendszer a tartalmazó típus egy másik részében deklarálja.

Példa: A következő részleges osztály két részből áll, amelyek különböző fordítási egységekben találhatók. Az első rész egy adatbázis-leképezési eszköz által létrehozott gép, a második rész pedig manuálisan jön létre:

public partial class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }
}

// File: Customer2.cs
public partial class Customer
{
    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

A fenti két rész együttes fordításakor az eredményül kapott kód úgy viselkedik, mintha az osztály egyetlen egységként lett volna megírva, az alábbiak szerint:

public class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }

    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

záró példa

A részleges típusdeklaráció különböző részeinek típusán vagy típusparaméterein megadott attribútumok kezelését a 22.3 tárgyalja.

15.3 Osztálytagok

15.3.1 Általános

Az osztály tagjai az class_member_declarationáltal bevezetett tagokból és a közvetlen alaposztályból öröklődő tagokból állnak.

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Az osztály tagjai a következő kategóriákba vannak osztva:

  • Állandók, amelyek az osztályhoz társított állandó értékeket jelölik (15.4. §).
  • Mezők, amelyek az osztály változói (15.5. §).
  • Metódusok, amelyek implementálják az osztály által végrehajtható számításokat és műveleteket (15.6. §).
  • Az elnevezett jellemzőket és a jellemzők olvasásához és írásához kapcsolódó műveleteket meghatározó tulajdonságok (15.7. §).
  • Az osztály által létrehozható értesítéseket meghatározó események (15.8. §).
  • Indexelők, amelyek lehetővé teszik, hogy az osztály példányai ugyanúgy (szintaktikailag) indexelhetők, mint a tömbök (15.9. §).
  • Operátorok, amelyek meghatározzák az osztály példányaira alkalmazható kifejezés operátorokat (15.10. §).
  • Az osztály példányainak inicializálásához szükséges műveleteket megvalósító példánykonstruktorok (15.11.§)
  • Véglegesítők, amelyek végrehajtják az osztálypéldányok végleges elvetése előtt végrehajtandó műveleteket (15.13. §).
  • Statikus konstruktorok, amelyek megvalósítják az osztály inicializálásához szükséges műveleteket (15.12.§).
  • Az osztályban helyi típusokat (14.7.§) képviselő típusok.

A class_declaration létrehoz egy új deklarációs területet (7.3.§), és az class_declaration által közvetlenül tartalmazott type_parameterés class_member_declarationúj tagokat vezetnek be ebbe a deklarációs térbe. A következő szabályok vonatkoznak class_member_declaration s-ekre:

  • A példánykonstruktoroknak, a véglegesítőknek és a statikus konstruktoroknak ugyanazzal a névvel kell rendelkezniük, mint az azonnal belefoglaló osztálynak. Minden más tagnak olyan neve van, amely különbözik az azonnal befoglaló osztály nevétől.

  • Az osztálydeklaráció type_parameter_list egy típusparaméter neve eltér az ugyanabban a type_parameter_list lévő összes többi típusparaméter nevétől, és különbözik az osztály nevétől és az osztály minden tagjának nevétől.

  • Egy típus neve eltér az ugyanabban az osztályban deklarált összes nem típusú tag nevétől. Ha két vagy több típusdeklaráció azonos teljes névvel rendelkezik, a deklarációknak rendelkezniük kell a partial módosítóval (15.2.7. §), és ezek a deklarációk egyetlen típus meghatározásához egyesíthetők.

Megjegyzés: Mivel egy típusdeklaráció teljes neve a típusparaméterek számát kódolja, két különböző típus ugyanazzal a névvel rendelkezhet, ha eltérő számú típusparaméterrel rendelkeznek. végjegyzet

  • Az állandó, mező, tulajdonság vagy esemény neve eltér az ugyanabban az osztályban deklarált többi tag nevétől.

  • A módszer neve eltér az ugyanabban az osztályban deklarált összes többi nem metódus nevétől. Ezenkívül egy módszer aláírása (7.6. §) különbözik az ugyanabban az osztályban deklarált összes többi módszer aláírásától, és az ugyanabban az osztályban deklarált két módszer nem rendelkezhet olyan aláírással, amely kizárólag inaz , outés ref.

  • A példánykonstruktor aláírása eltér az ugyanabban az osztályban deklarált összes többi példánykonstruktor aláírásától, és az ugyanabban az osztályban deklarált két konstruktor nem rendelkezhet olyan aláírásokkal, amelyek kizárólag ref és out.

  • Az indexelő aláírásának különböznie kell az azonos osztályban deklarált összes többi indexelő aláírásától.

  • Az üzemeltető aláírása eltér az azonos osztályba sorolt összes többi gazdasági szereplő aláírásától.

Az osztály örökölt tagjai (15.3.4. §) nem részei az osztály deklarációs terének.

Megjegyzés: Így egy származtatott osztály az öröklött taggal azonos nevű vagy aláírású tagot deklarálhat (ami valójában elrejti az örökölt tagot). végjegyzet

A többrészesen deklarált típusú tagok halmaza (15.2.7. §) az egyes részekben deklarált tagok egysége. A típusbevallás 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. Bármely tag akadálymentességi tartománya mindig tartalmazza a beágyazási típus összes részét; az egyik részben deklarált magánszemély szabadon elérhető egy másik részből. Fordítási idejű hiba, ha ugyanazon tagot a típus több részében is deklarálja, kivéve, ha az adott tag rendelkezik a partial módosítóval.

Példa:

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M();        // Ok, defining partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int y;
    }
}

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M() { }     // Ok, implementing partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int z;
    }
}

záró példa

A mező inicializálási sorrendje jelentős lehet a C# kódban , és a 15.5.6.1. Ellenkező esetben a tagok egy típuson belüli sorrendje ritkán jelentős, de más nyelvekkel és környezetekkel való együttműködés esetén jelentős lehet. Ezekben az esetekben a tagok több részre deklarált típuson belüli sorrendje nem határozható meg.

15.3.2 A példány típusa

Minden osztálydeklarációhoz társított példánytípus tartozik. Általános osztálydeklaráció esetén a példánytípus úgy jön létre, hogy létrehoz egy létrehozott típust (8.4. §) a típusdeklarációból, és a megadott típusargumentumok mindegyike a megfelelő típusparaméter. Mivel a példánytípus a típusparamétereket használja, csak ott használható, ahol a típusparaméterek hatókörben vannak; vagyis az osztálydeklaráción belül. A példány típusa az osztálydeklarációban írt kód típusa this . Nem általános osztályok esetén a példánytípus egyszerűen a deklarált osztály.

Példa: Az alábbiakban számos osztálydeklaráció látható a példánytípusokkal együtt:

class A<T>             // instance type: A<T>
{
    class B {}         // instance type: A<T>.B
    class C<U> {}      // instance type: A<T>.C<U>
}
class D {}             // instance type: D

záró példa

15.3.3 Az épített típusok tagjai

Az épített típus nem öröklődő tagjait a tagnyilatkozatban szereplő type_parameter az épített típus megfelelő type_argument helyettesítésével nyerik ki. A helyettesítési folyamat a típusdeklarációk szemantikai jelentésén alapul, és nem csupán szöveges helyettesítés.

Példa: Az általános osztály deklarációja

class Gen<T,U>
{
    public T[,] a;
    public void G(int i, T t, Gen<U,T> gt) {...}
    public U Prop { get {...} set {...} }
    public int H(double d) {...}
}

a létrehozott típus Gen<int[],IComparable<string>> a következő tagokat tartalmazza:

public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}

Az általános osztálydeklarációban szereplő tag a típusa a "kétdimenziós tömb"Gen, ezért a fenti ábrán szereplő tag T típusa "egydimenziós tömb kétdimenziós tömbjea" vagy int.int[,][]

záró példa

A példányfüggvény tagjain belül a this típus az tartalmazó deklaráció példánytípusa (15.3.2. §).

Az általános osztály minden tagja használhat típusparamétereket bármely befoglaló osztályból, akár közvetlenül, akár egy létrehozott típus részeként. Ha futásidőben egy adott zárt építésű típust (8.4.3. §) használnak, a típusparaméter minden egyes használata a létrehozott típushoz megadott típusargumentumra kerül.

Példa:

class C<V>
{
    public V f1;
    public C<V> f2;

    public C(V x)
    {
        this.f1 = x;
        this.f2 = this;
    }
}

class Application
{
    static void Main()
    {
        C<int> x1 = new C<int>(1);
        Console.WriteLine(x1.f1);              // Prints 1

        C<double> x2 = new C<double>(3.1415);
        Console.WriteLine(x2.f1);              // Prints 3.1415
    }
}

záró példa

15.3.4 Öröklés

Az osztály a közvetlen alaposztály tagjait örökli. Az öröklés azt jelenti, hogy az osztály implicit módon tartalmazza a közvetlen alaposztály összes tagját, kivéve az alaposztály példánykonstruktorait, véglegesítőit és statikus konstruktorait. Az öröklés néhány fontos aspektusa:

  • Az öröklés tranzitív. Ha C származik B, és B származik A, akkor C örökli a tagokat deklaráltB, valamint a tagok deklarált.A

  • A származtatott osztály kiterjeszti a közvetlen alaposztályát. A származtatott osztályok új tagokat adhatnak hozzá az örökölt tagokhoz, de nem távolíthatják el az örökölt tagok definícióját.

  • A példánykonstruktorok, a véglegesítők és a statikus konstruktorok nem öröklődnek, de az összes többi tag a deklarált akadálymentességtől függetlenül (7.5. §). A deklarált akadálymentességtől függően azonban előfordulhat, hogy az örökölt tagok nem érhetők el származtatott osztályban.

  • A származtatott osztály elrejtheti az örökölt tagokat (7.7.2.3.§), ha új tagokat deklarál ugyanazzal a névvel vagy aláírással. Az öröklődő tag elrejtése azonban nem távolítja el a tagot – csupán azt teszi elérhetetlenné közvetlenül a származtatott osztályon keresztül.

  • Az osztály egy példánya tartalmazza az osztályban és annak alaposztályaiban deklarált összes példánymezőt, és implicit átalakítás (10.2.8. §) létezik egy származtatott osztálytípusból bármely alaposztálytípusra. Így egy származtatott osztály egy példányára való hivatkozás bármely alaposztályának egy példányára való hivatkozásként kezelhető.

  • Az osztályok deklarálhatnak virtuális metódusokat, tulajdonságokat, indexelőket és eseményeket, és a származtatott osztályok felülírhatják ezeknek a függvénytagoknak a megvalósítását. Ez lehetővé teszi az osztályok számára, hogy polimorfikus viselkedést tanúsítanak, amelyben a függvénytagok meghívása által végrehajtott műveletek a függvénytag meghívásának futásidejű típusától függően változnak.

A létrehozott osztálytípusok öröklődő tagjai az azonnali alaposztálytípus tagjai (15.2.4.2. §), amely a base_class_specification megfelelő típusparaméterek minden előfordulására a létrehozott típus típusargumentumainak helyettesítésével található. Ezek a tagok viszont a tagok nyilatkozatában szereplő type_parameter a base_class_specification megfelelő type_argumenthelyettesítésével alakulnak át.

Példa:

class B<U>
{
    public U F(long index) {...}
}

class D<T> : B<T[]>
{
    public T G(string s) {...}
}

A fenti kódban a létrehozott típus D<int> egy nem öröklődő nyilvános tagot intG(string s) kap a típusparaméter inttípusargumentumának T helyettesítésével. D<int> az osztály deklarációjának Böröklődő tagja is van. Ezt az örökölt tagot úgy határozzuk meg, hogy először az alaposztály típusát B<int[]>D<int> az alaposztály specifikációjának inthelyettesítésével TB<T[]> határozzuk meg . Ezt követően a típusargumentum Bint[] helyett a függvény az Upublic U F(long index)öröklött tagot public int[] F(long index)adja vissza .

záró példa

15.3.5 Az új módosító

A class_member_declaration egy öröklött taggal azonos nevű vagy aláírású tagot deklarálhat. Ha ez történik, a származtatott osztálytag azt mondja, hogy elrejtse az alaposztály tagját. A 7.7.2.3 . pontban pontosan meg kell határozni, hogy egy tag mikor rejt el öröklött tagot.

Az örökölt tag M akkor tekinthető elérhetőnek, ha M elérhető, és nincs más örökölt akadálymentes N tag, amely már elrejtiM. Az örökölt tag implicit elrejtése nem minősül hibának, de a fordító figyelmeztetést ad ki, kivéve, ha a származtatott osztálytag deklarációja tartalmaz egy new módosítót, amely kifejezetten jelzi, hogy a származtatott tag célja az alaptag elrejtése. Ha egy beágyazott típus részleges deklarációjának egy vagy több része (15.2.7. §) tartalmazza a new módosítót, a rendszer nem küld figyelmeztetést, ha a beágyazott típus elrejt egy elérhető öröklött tagot.

Ha egy new módosító szerepel egy olyan deklarációban, amely nem rejt el egy elérhető öröklött tagot, a rendszer figyelmeztetést küld erre az effektusra.

15.3.6 Hozzáférési módosítók

A class_member_declaration a deklarált akadálymentesség bármely engedélyezett típusával rendelkezhetnek (7.5.2.§): public, protected internal, , protected, private protectedinternalvagy private. A kombinációk és protected internal a private protected kombinációk kivételével fordítási idejű hiba egynél több hozzáférés-módosító megadása. Ha egy class_member_declaration nem tartalmaz hozzáférési módosítókat, private feltételezzük.

15.3.7 Összetevők típusai

A tag deklarációjában használt típusokat ennek a tagnak a rendszerösszetevő típusainak nevezzük. Lehetséges összetevőtípusok az állandó, mező, tulajdonság, esemény vagy indexelő típusa, egy metódus vagy operátor visszatérési típusa, valamint egy metódus, indexelő, operátor vagy példánykonstruktor paramétertípusai. A tag alkotó típusainak legalább olyan hozzáférhetőnek kell lenniük, mint maga a tag (7.5.5.5. §).

15.3.8 Statikus és példánytagok

Az osztály tagjai statikus vagy példánytagok.

Megjegyzés: Általánosságban elmondható, hogy hasznos lehet úgy tekinteni a statikus tagokra, mint amelyek az osztályokhoz és a példányok tagjaihoz tartoznak, mint objektumokhoz (osztályok példányaihoz). végjegyzet

Ha egy mező, metódus, tulajdonság, esemény, operátor vagy konstruktor-deklaráció módosítót static tartalmaz, statikus tagot deklarál. Emellett egy állandó vagy típusdeklaráció implicit módon deklarál egy statikus tagot. A statikus tagok a következő jellemzőkkel rendelkeznek:

  • Ha egy statikus tagra az űrlap member_access hivatkozik (M. §), akkor egy tagot tartalmazó típust kell jelölnie .E.ME Ez egy példány jelölésére vonatkozó fordítási E időhiba.
  • A nem általános osztály statikus mezői pontosan egy tárolóhelyet azonosítanak. Nem számít, hogy egy nem általános osztály hány példánya jön létre, egy statikus mezőnek csak egy példánya van. Minden különálló zárt építésű típus (8.4.3. §) saját statikus mezőkészlettel rendelkezik, függetlenül a bezárt épített típus példányainak számától.
  • A statikus függvénytagok (metódus, tulajdonság, esemény, operátor vagy konstruktor) nem egy adott példányon működnek, és fordítási idő hibát jelent erre hivatkozni egy ilyen függvénytagban.

Ha egy mező, metódus, tulajdonság, esemény, indexelő, konstruktor vagy véglegesítő deklaráció nem tartalmaz statikus módosítót, akkor egy példánytagot deklarál. (A példánytagokat néha nem statikus tagnak is nevezik.) A példányok tagjai a következő jellemzőkkel rendelkeznek:

  • Ha egy példánytagra Maz űrlap egy member_access hivatkozik (E.M. §), egy olyan példányt jelöl , E amelynek tagja Mvan . Az E kötési idő hibája egy típus jelölésére.
  • Az osztály minden példánya külön halmazt tartalmaz az osztály összes példánymezője közül.
  • A példányfüggvény-tag (metódus, tulajdonság, indexelő, példánykonstruktor vagy véglegesítő) az osztály egy adott példányán működik, és ez a példány a (this. §) néven érhető el.

Példa: Az alábbi példa a statikus és példánytagok elérésére vonatkozó szabályokat mutatja be:

class Test
{
    int x;
    static int y;
    void F()
    {
        x = 1;               // Ok, same as this.x = 1
        y = 1;               // Ok, same as Test.y = 1
    }

    static void G()
    {
        x = 1;               // Error, cannot access this.x
        y = 1;               // Ok, same as Test.y = 1
    }

    static void Main()
    {
        Test t = new Test();
        t.x = 1;       // Ok
        t.y = 1;       // Error, cannot access static member through instance
        Test.x = 1;    // Error, cannot access instance member through type
        Test.y = 1;    // Ok
    }
}

A F módszer azt mutatja, hogy egy példányfüggvény-tagban egy simple_name (12.8.4. §) használható a példánytagok és a statikus tagok elérésére. A G módszer azt mutatja, hogy egy statikus függvénytagban fordítási idő hibát jelent egy példánytag elérése egy simple_name keresztül. A Main módszer azt mutatja, hogy egy member_access (12.8.7. §) a példányok tagjait példányokon keresztül, a statikus tagokat pedig típusokkal kell elérni.

záró példa

15.3.9 Beágyazott típusok

15.3.9.1 Általános

Az osztályon vagy szerkezeten belül deklarált típusokat beágyazott típusnak nevezzük. A fordítási egységben vagy névtérben deklarált típusokat nem beágyazott típusnak nevezzük.

Példa: Az alábbi példában:

class A
{
    class B
    {
        static void F()
        {
            Console.WriteLine("A.B.F");
        }
    }
}

az osztály B beágyazott típus, mert az osztályon Abelül deklarálva van, és az osztály A nem beágyazott típus, mert egy fordítási egységen belül deklarálva van.

záró példa

15.3.9.2 Teljes név

A beágyazott típusdeklaráció teljes neve (§7.8.3) S.N, ahol S annak a típusdeklarációnak a teljes neve, amelyben a(z) N típus deklarálva van, és a beágyazott típusdeklaráció nem minősített neve (N) a(z) , beleértve a generic_dimension_specifier (§12.8.18).

15.3.9.3 Deklarált akadálymentesség

A nem beágyazott típusok rendelkezhetnek vagy public deklarálhatnak internal akadálymentességet, és alapértelmezés szerint akadálymentességet deklaráltakinternal. A beágyazott típusok a deklarált akadálymentesség ezen formáival, valamint a deklarált akadálymentesség egy vagy több további formájával is rendelkezhetnek, attól függően, hogy a beágyazott típus osztály vagy struktúra:

  • Az osztályban deklarált beágyazott típusok bármelyik engedélyezett típusú deklarált akadálymentességgel rendelkezhetnek, és a többi osztálytaghoz private hasonlóan alapértelmezés szerint a deklarált akadálymentesség.
  • Egy strukturált szerkezetben deklarált beágyazott típus a deklarált akadálymentességpublic (vagy internal) három formájának bármelyikével rendelkezhet, privateés a többi strukturált taghoz private hasonlóan alapértelmezés szerint a deklarált akadálymentesség.

Példa: A példa

public class List
{
    // Private data structure
    private class Node
    {
        public object Data;
        public Node? Next;

        public Node(object data, Node? next)
        {
            this.Data = data;
            this.Next = next;
        }
    }

    private Node? first = null;
    private Node? last = null;

    // Public interface
    public void AddToFront(object o) {...}
    public void AddToBack(object o) {...}
    public object RemoveFromFront() {...}
    public object RemoveFromBack() {...}
    public int Count { get {...} }
}

privát beágyazott osztályt Nodedeklarál.

záró példa

15.3.9.4 Elrejtés

A beágyazott típus elrejtheti az alaptagot (7.7.2.2.2. §). A new módosító (15.3.5. §) beágyazott típusdeklarációkon engedélyezett, így a elrejtés explicit módon kifejezhető.

Példa: A példa

class Base
{
    public static void M()
    {
        Console.WriteLine("Base.M");
    }
}

class Derived: Base
{
    public new class M
    {
        public static void F()
        {
            Console.WriteLine("Derived.M.F");
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.M.F();
    }
}

egy beágyazott osztályt M jelenít meg, amely elrejti a megadott metódustM.Base

záró példa

15.3.9.5 ez a hozzáférés

A beágyazott típus és a benne található típus nem áll különleges kapcsolatban a this_access tekintetében (12.8.14. §). this A beágyazott típuson belül nem lehet hivatkozni azokat a példányokat tartalmazó típus tagjaira. Azokban az esetekben, amikor a beágyazott típusnak hozzáférésre van szüksége a beágyazott típus példánytagjaihoz, a hozzáférést úgy lehet biztosítani, hogy a this beágyazott típus konstruktor argumentumaként megadja a beágyazott típus példányát.

Példa: Az alábbi példa

class C
{
    int i = 123;
    public void F()
    {
        Nested n = new Nested(this);
        n.G();
    }

    public class Nested
    {
        C this_c;

        public Nested(C c)
        {
            this_c = c;
        }

        public void G()
        {
            Console.WriteLine(this_c.i);
        }
    }
}

class Test
{
    static void Main()
    {
        C c = new C();
        c.F();
    }
}

ezt a technikát mutatja be. A példányok C létrehoznak Nestedegy példányt, és átadják a Nestedsaját példányát a konstruktornak, hogy későbbi hozzáférést biztosítsanak a példánytagok számára C.

záró példa

15.3.9.6 Hozzáférés a tartalmú típusú magán- és védett tagokhoz

A beágyazott típus hozzáféréssel rendelkezik az összes olyan taghoz, amely elérhető a benne található típushoz, beleértve az akadálymentességet deklaráló és tartalmazó típus privateprotected tagjait is.

Példa: A példa

class C
{
    private static void F() => Console.WriteLine("C.F");

    public class Nested
    {
        public static void G() => F();
    }
}

class Test
{
    static void Main() => C.Nested.G();
}

beágyazott osztályt tartalmazó osztályt CNestedjelenít meg. Ezen belül Nesteda metódus G meghívja a megadott Fstatikus metódustC, és F privát deklarált akadálymentességgel rendelkezik.

záró példa

A beágyazott típus is hozzáférhet a védett tagokhoz, amelyek a benne található alaptípusban definiálva találhatók.

Példa: Az alábbi kódban

class Base
{
    protected void F() => Console.WriteLine("Base.F");
}

class Derived: Base
{
    public class Nested
    {
        public void G()
        {
            Derived d = new Derived();
            d.F(); // ok
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.Nested n = new Derived.Nested();
        n.G();
    }
}

a beágyazott osztály Derived.Nested az alaposztályban Fdefiniált védett metódushoz Derived fér hozzá egy Basepéldány Derivedmeghívásával.

záró példa

15.3.9.7 Beágyazott típusok általános osztályokban

Az általános osztálydeklarációk beágyazott típusdeklarációkat tartalmazhatnak. A beágyazási osztály típusparaméterei a beágyazott típusok között használhatók. A beágyazott típusdeklarációk olyan további típusparamétereket tartalmazhatnak, amelyek csak a beágyazott típusra vonatkoznak.

Az általános osztálydeklarációkban található típusdeklarációk implicit módon általános típusdeklarációk. Egy általános típusba beágyazott típusra mutató hivatkozás írásakor el kell nevezni a létrehozott típust, beleértve annak típusargumentumait is. A külső osztályból azonban a beágyazott típus minősítés nélkül is használható; a külső osztály példánytípusa implicit módon használható a beágyazott típus létrehozásakor.

Példa: Az alábbiakban három különböző helyes módszert mutatunk be egy létrehozott típusra Innervaló hivatkozáshoz; az első kettő egyenértékű:

class Outer<T>
{
    class Inner<U>
    {
        public static void F(T t, U u) {...}
    }

    static void F(T t)
    {
        Outer<T>.Inner<string>.F(t, "abc");    // These two statements have
        Inner<string>.F(t, "abc");             // the same effect
        Outer<int>.Inner<string>.F(3, "abc");  // This type is different
        Outer.Inner<string>.F(t, "abc");       // Error, Outer needs type arg
    }
}

záró példa

Bár rossz programozási stílus, a beágyazott típusparaméterek elrejthetik a külső típusban deklarált tag- vagy típusparamétereket.

Példa:

class Outer<T>
{
    class Inner<T>                                  // Valid, hides Outer's T
    {
        public T t;                                 // Refers to Inner's T
    }
}

záró példa

15.3.10 Fenntartott tagok neve

15.3.10.1 Általános

A mögöttes C# futásidejű végrehajtás megkönnyítése érdekében minden olyan forrástag-deklaráció esetében, amely tulajdonság, esemény vagy indexelő, a végrehajtásnak két metódusaláírást kell lefoglalnia a tag deklaráció típusa, neve és típusa alapján (15.3.10.2. §, 15.3.10.3. §, 15.3.10.4. §). Fordítási idő hibája, hogy egy program deklarál egy olyan tagot, akinek az aláírása megegyezik az azonos hatókörben deklarált tag által fenntartott aláírással, még akkor is, ha az alapul szolgáló futásidejű megvalósítás nem használja ezeket a foglalásokat.

A fenntartott nevek nem vezetnek be deklarációkat, így nem vesznek részt a tagok keresésében. A deklarációhoz társított fenntartott módszer aláírásai azonban részt vesznek az öröklésben (15.3.4. §), és elrejthetők a new módosítóval (15.3.5. §).

Megjegyzés: A nevek foglalása három célt szolgál:

  1. Annak lehetővé tétele, hogy az alapul szolgáló implementáció egy szokásos azonosítót használjon metódusnévként a C# nyelvi funkcióhoz való hozzáférés lekéréséhez vagy beállításához.
  2. Annak lehetővé tétele, hogy más nyelvek is együttműködhessenek egy szokásos azonosítóval a C# nyelvi funkcióhoz való hozzáférés lekéréséhez vagy beállításához.
  3. Annak biztosítása érdekében, hogy az egyik megfelelő fordító által elfogadott forrást egy másik fogadja el, a fenntartott tagnevek jellemzőinek konzisztenssé tételével az összes C#-implementációban.

végjegyzet

A véglegesítő nyilatkozata (15.13.§) szintén fenntartást okoz az aláírásnak (15.3.10.5. §).

Bizonyos nevek operátor-metódusnevekként vannak fenntartva (15.3.10.6. §).

15.3.10.2 A tulajdonságokhoz fenntartott tagnevek

Egy (P.)-es típusú tulajdonság T esetében a következő aláírások vannak fenntartva:

T get_P();
void set_P(T value);

Mindkét aláírás fenntartott, még akkor is, ha a tulajdonság írásvédett vagy írásvédett.

Példa: Az alábbi kódban

class A
{
    public int P
    {
        get => 123;
    }
}

class B : A
{
    public new int get_P() => 456;

    public new void set_P(int value)
    {
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        Console.WriteLine(a.P);
        Console.WriteLine(b.P);
        Console.WriteLine(b.get_P());
    }
}

Az osztály A egy írásvédett tulajdonságot Phatároz meg, így megőrizve az aláírásokat és get_P a set_P metódusokat. A osztály B mindkét fenntartott aláírásból származik A , és elrejti őket. A példa a kimenetet hozza létre:

123
123
456

záró példa

15.3.10.3 Eseményekhez fenntartott tagnevek

Meghatalmazott típusú Eesemény esetén (T. §) a következő aláírások vannak fenntartva:

void add_E(T handler);
void remove_E(T handler);

15.3.10.4 Az indexelők számára fenntartott tagnevek

Paraméterlistával rendelkező indexelő (T.§) L esetén a következő aláírások vannak fenntartva:

T get_Item(L);
void set_Item(L, T value);

Mindkét aláírás fenntartott, még akkor is, ha az indexelő írásvédett vagy írásvédett.

Továbbá a tag neve Item fenntartott.

15.3.10.5 Véglegesítésre fenntartott tagnevek

Véglegesítőt tartalmazó osztály esetén (15.13.§) a következő aláírás van fenntartva:

void Finalize();

15.3.10.6 Operátorok számára fenntartott metódusnevek

A következő metódusnevek vannak fenntartva. Bár sokan rendelkeznek megfelelő operátorokkal ebben a specifikációban, néhányat a jövőbeli verziók számára, míg mások más nyelvekkel való együttműködésre vannak fenntartva.

Metódus neve C# operátor
op_Addition + (bináris)
op_AdditionAssignment (fenntartott)
op_AddressOf (fenntartott)
op_Assign (fenntartott)
op_BitwiseAnd & (bináris)
op_BitwiseAndAssignment (fenntartott)
op_BitwiseOr \|
op_BitwiseOrAssignment (fenntartott)
op_CheckedAddition (jövőbeli használatra fenntartva)
op_CheckedDecrement (jövőbeli használatra fenntartva)
op_CheckedDivision (jövőbeli használatra fenntartva)
op_CheckedExplicit (jövőbeli használatra fenntartva)
op_CheckedIncrement (jövőbeli használatra fenntartva)
op_CheckedMultiply (jövőbeli használatra fenntartva)
op_CheckedSubtraction (jövőbeli használatra fenntartva)
op_CheckedUnaryNegation (jövőbeli használatra fenntartva)
op_Comma (fenntartott)
op_Decrement -- (előtag és utótag)
op_Division /
op_DivisionAssignment (fenntartott)
op_Equality ==
op_ExclusiveOr ^
op_ExclusiveOrAssignment (fenntartott)
op_Explicit explicit (szűkítő) kényszerítés
op_False false
op_GreaterThan >
op_GreaterThanOrEqual >=
op_Implicit implicit (szélesítési) kényszerítés
op_Increment ++ (előtag és utótag)
op_Inequality !=
op_LeftShift <<
op_LeftShiftAssignment (fenntartott)
op_LessThan <
op_LessThanOrEqual <=
op_LogicalAnd (fenntartott)
op_LogicalNot !
op_LogicalOr (fenntartott)
op_MemberSelection (fenntartott)
op_Modulus %
op_ModulusAssignment (fenntartott)
op_MultiplicationAssignment (fenntartott)
op_Multiply * (bináris)
op_OnesComplement ~
op_PointerDereference (fenntartott)
op_PointerToMemberSelection (fenntartott)
op_RightShift >>
op_RightShiftAssignment (fenntartott)
op_SignedRightShift (fenntartott)
op_Subtraction - (bináris)
op_SubtractionAssignment (fenntartott)
op_True true
op_UnaryNegation - (egyváltozós)
op_UnaryPlus + (egyváltozós)
op_UnsignedRightShift (jövőbeli használatra fenntartva)
op_UnsignedRightShiftAssignment (fenntartott)

15.4 Állandók

Az állandó egy olyan osztálytag, amely állandó értéket jelöl: olyan értéket, amely fordításkor számítható ki. Egy constant_declaration egy vagy több konstanst vezet be egy adott típusból.

constant_declaration
    : attributes? constant_modifier* 'const' type constant_declarators ';'
    ;

constant_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    ;

A constant_declaration tartalmazhat attribútumkészletet (22. §), new módosítót (15.3.5. §) és a deklarált akadálymentesség bármely megengedett típusát (15.3.6. §). Az attribútumok és módosítók a constant_declaration által deklarált összes tagra vonatkoznak. Annak ellenére, hogy az állandók statikus tagok, a constant_declaration sem igényelnek, sem nem teszik lehetővé a static módosító használatát. Hiba, hogy ugyanaz a módosító többször is megjelenik egy állandó deklarációban.

A constant_declaration típusahatározza meg a deklaráció által bevezetett tagok típusát. A típust a constant_declaratorlistája követi (13.6.3. §), amelyek mindegyike új tagot vezet be. A constant_declarator egy olyan azonosítóból áll, amely a tag nevét, majd egy "=" jogkivonatot, majd egy constant_expression (12.23. §) követi, amely megadja a tag értékét.

Az állandó deklarációban megadott típus: sbyte, byte, short, ushort, int, uintlongulongcharfloatdoubledecimalboolstringenum_type vagy reference_type. Minden constant_expression a céltípus vagy egy olyan típus értékét kell eredményeznie, amely implicit átalakítással átalakítható céltípussá (10.2. §).

Az állandó típusának legalább olyan akadálymentesnek kell lennie, mint maga az állandó (7.5.5. §).

Az állandó értékét egy kifejezésben simple_name (12.8.4. §) vagy member_access (12.8.7. §) használatával nyerik ki.

Az állandó maga is részt vehet egy constant_expression. Így egy állandó használható minden olyan szerkezetben, amely constant_expression igényel.

Megjegyzés: Ilyen szerkezetek például a case címkék, utasítások, goto caseenum tagdeklarációk, attribútumok és egyéb állandó deklarációk. végjegyzet

Megjegyzés: A 12.23. §-ban leírtak szerint a constant_expression olyan kifejezés, amely fordításkor teljes mértékben kiértékelhető. Mivel egy reference_type stringlétrehozásának egyetlen módja az operátor alkalmazásanew, és mivel az new operátor nem engedélyezett egy constant_expression, a reference_type stringegyetlen lehetséges értéke nem az.null végjegyzet

Ha egy állandó érték szimbolikus neve szükséges, de ha az érték típusa nem engedélyezett állandó deklarációban, vagy ha az érték fordításkor nem számítható ki egy constant_expression, akkor helyette egy olvasható mező (15.5.3. §) használható.

Megjegyzés: A verziószámozás szemantikája és const eltérései readonly (15.5.3.3.3.). végjegyzet

A több állandót deklaráló állandó deklaráció egyenértékű az azonos attribútumokkal, módosítókkal és típussal rendelkező állandók több deklarációjával.

Példa:

class A
{
    public const double X = 1.0, Y = 2.0, Z = 3.0;
}

egyenértékű a

class A
{
    public const double X = 1.0;
    public const double Y = 2.0;
    public const double Z = 3.0;
}

záró példa

Az állandók csak akkor függhetnek ugyanazon a programon belül más állandóktól, ha a függőségek nem körkörös jellegűek.

Példa: Az alábbi kódban

class A
{
    public const int X = B.Z + 1;
    public const int Y = 10;
}

class B
{
    public const int Z = A.Y + 1;
}

A fordítónak először ki kell értékelnie A.Y-t, majd B.Z-et, és végül A.X-t, ezáltal előállítva a 10, 11és 12értékeket.

záró példa

Az állandó deklarációk függhetnek más programok állandóitól, de ezek a függőségek csak egy irányban lehetségesek.

Példa: A fenti példára hivatkozva, ha AB külön programokban deklarálták őket, akkor lehet A.X függeni B.Z, de B.Z nem lehet egyszerre függeni A.Y. záró példa

15.5 Mezők

15.5.1 Általános

A mező olyan tag, amely egy objektumhoz vagy osztályhoz társított változót jelöl. Egy field_declaration egy vagy több, adott típusú mezőt vezet be.

field_declaration
    : attributes? field_modifier* type variable_declarators ';'
    ;

field_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'readonly'
    | 'volatile'
    | unsafe_modifier   // unsafe code support
    ;

variable_declarators
    : variable_declarator (',' variable_declarator)*
    ;

variable_declarator
    : identifier ('=' variable_initializer)?
    ;

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

A field_declaration tartalmazhat attribútumkészletet (22. §), new módosítót (15.3.5. §), a négy hozzáférési módosító érvényes kombinációját (15.3.6. §) és egy static módosítót (15.5.2. §). Ezenkívül a field_declaration lehetnek readonly módosító (15.5.3. §) vagy volatile módosító (15.5.4. §), de mindkettő nem. Az attribútumok és módosítók a field_declaration által deklarált összes tagra vonatkoznak. Hiba, hogy ugyanaz a módosító többször is megjelenik egy field_declaration.

A field_declaration típusahatározza meg a deklaráció által bevezetett tagok típusát. A típust a variable_declaratorlistája követi, amelyek mindegyike új tagot vezet be. A variable_declarator egy olyan azonosítóból áll, amely a tag nevét adja, amelyet opcionálisan egy "=" jogkivonat és egy variable_initializer (15.5.6. §) követ, amely megadja az adott tag kezdeti értékét.

A mező típusának legalább olyan hozzáférhetőnek kell lennie, mint maga a mező (7.5.5. §).

A mező értékét egy kifejezésben simple_name (12.8.4.§), member_access (12.8.7. §) vagy base_access (12.8.15. §) használatával lehet kinyerni. A nem olvasható mezők értéke hozzárendeléssel módosul (12.21.§). A nem olvasható mezők értéke postfix növekményes és decrement operátorokkal (12.8.16.§) és előtag-növekményes és decrement operátorokkal (12.9.6. §) is beszerezhető és módosítható.

A több mezőt deklaráló meződeklaráció egyenértékű az azonos attribútumokkal, módosítókkal és típusokkal rendelkező mezők több deklarációjával.

Példa:

class A
{
    public static int X = 1, Y, Z = 100;
}

egyenértékű a

class A
{
    public static int X = 1;
    public static int Y;
    public static int Z = 100;
}

záró példa

15.5.2 Statikus és példánymezők

Ha egy meződeklaráció módosítót static tartalmaz, a deklaráció által bevezetett mezők statikus mezők. Ha nincs static módosító, a deklaráció által bevezetett mezők példánymezők. A statikus mezők és a példánymezők a C# által támogatott különböző változók (§9) közül kettő, és időnként statikus változóknak és példányváltozóknak is nevezik őket.

A 15.3.8. §-ban leírtak szerint az osztály minden példánya tartalmazza az osztály példánymezőinek teljes készletét, míg minden nem általános vagy zárt, konfigurálható típushoz csak egy statikus mezőkészlet tartozik, függetlenül az osztály példányainak számától vagy a bezárt épített típustól.

15.5.3 Olvasható mezők

15.5.3.1 Általános

Ha egy field_declaration tartalmaz módosítót, a deklaráció által bevezetett mezők írásvédett mezőkreadonly. A közvetlen hozzárendelések írásvédett mezőkhöz csak a deklaráció részeként, vagy egy példánykonstruktorban vagy statikus konstruktorban történhetnek ugyanabban az osztályban. (Ezekben a környezetekben egy olvasható mező többször is hozzárendelhető.) A közvetlen hozzárendelések írásvédett mezőkhöz csak a következő környezetekben engedélyezettek:

  • A mezőt tartalmazó variable_declarator (egy variable_initializer beleszámítva a deklarációba).
  • Példánymező esetén a meződeklarációt tartalmazó osztály példánykonstruktoraiban; statikus mező esetén a meződeklarációt tartalmazó osztály statikus konstruktorában. Ezek az egyetlen környezetek, amelyekben érvényes egy olvasható mező átadása kimeneti vagy referenciaparaméterként.

Fordítási időhiba, ha egy írásvédett mezőhöz próbál meg hozzárendelni, vagy kimeneti vagy referenciaparaméterként átadni azt bármely más környezetben.

15.5.3.2 Statikus írásvédett mezők használata állandókhoz

A statikus írásvédett mező akkor hasznos, ha egy állandó érték szimbolikus nevét szeretné megadni, de ha az érték típusa nem engedélyezett a const deklarációban, vagy ha az érték fordításkor nem számítható ki.

Példa: Az alábbi kódban

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

a Black, White, RedGreenés Blue a tagok nem deklarálhatók const tagokként, mert értékük fordításkor nem számítható ki. A deklarálásnak static readonly azonban sokkal ugyanaz a hatása.

záró példa

15.5.3.3 Állandók és statikus írásvédett mezők verziószámozása

Az állandók és az olvasható mezők eltérő bináris verziószámozási szemantikával rendelkeznek. Ha egy kifejezés konstansra hivatkozik, az állandó értékét fordítási időpontban kapja meg a rendszer, de ha egy kifejezés írásvédett mezőre hivatkozik, a mező értéke csak futásidőben lesz beolvasva.

Példa: Vegyünk egy alkalmazást, amely két különálló programból áll:

namespace Program1
{
    public class Utils
    {
        public static readonly int x = 1;
    }
}

és

namespace Program2
{
    class Test
    {
        static void Main()
        {
            Console.WriteLine(Program1.Utils.X);
        }
    }
}

A Program1 névterek két Program2 külön lefordított programot jelölnek. Mivel Program1.Utils.X mezőként static readonly van deklarálva, az Console.WriteLine utasítás által előállított érték fordítási időben nem ismert, hanem futásidőben lesz beállítva. Így ha az érték X módosul, és Program1 újrafordított, az Console.WriteLine utasítás akkor is kiadja az új értéket, ha Program2 nem fordítja újra. Ha azonban állandó lett volnaX, akkor az érték X a fordításkor Program2 lett volna beállítva, és az újrafordításig Program1 változatlan Program2 maradna.

záró példa

15.5.4 Változó mezők

Ha egy field_declaration tartalmaz módosítót, a deklarációvolatile. A nem változékony mezők esetében az utasítások átrendezésére szolgáló optimalizálási technikák váratlan és kiszámíthatatlan eredményekhez vezethetnek többszálú programokban, amelyek szinkronizálás nélkül férnek hozzá a mezőkhöz, például a lock_statement által biztosítottakhoz (13.13.§). Ezeket az optimalizálásokat a fordító, a futásidejű rendszer vagy a hardver végezheti el. A változó mezők esetében az ilyen átrendezési optimalizálások korlátozottak:

  • Az illékony mezők olvasását illékony olvasásnak nevezzük. A volatilis olvasás "szemantikát szerez be"; vagyis az utasításütemezésben a memóriare való hivatkozás előtt garantáltan bekövetkezik.
  • Az illékony mezők írását illékony írásnak nevezzük. A volatilis írás "kiadási szemantikával" rendelkezik; ez azt jelenti, hogy az utasításütemezés írási utasítása előtti memóriahivatkozások után garantáltan bekövetkezik.

Ezek a korlátozások biztosítják, hogy minden szál megfigyelje a más szálak által végzett illékony írásokat a végrehajtásuk sorrendjében. A megfelelő megvalósítás nem szükséges ahhoz, hogy egyetlen teljes sorrendbe rendezze az illékony írásokat, ahogy az a végrehajtás összes szálából látható. Az illékony mező típusának a következők egyike kell, hogy legyen:

  • Egy reference_type.
  • Referenciatípusként ismert type_parameter (15.2.5. §).
  • A típus byte, sbyte, short, ushort, intuint, char, float, bool, System.IntPtr, vagy System.UIntPtr.
  • Olyan enum_type , amely enum_base típusú byte, sbyte, short, ushort, intvagy uint.

Példa: A példa

class Test
{
    public static int result;
    public static volatile bool finished;

    static void Thread2()
    {
        result = 143;
        finished = true;
    }

    static void Main()
    {
        finished = false;

        // Run Thread2() in a new thread
        new Thread(new ThreadStart(Thread2)).Start();    

        // Wait for Thread2() to signal that it has a result
        // by setting finished to true.
        for (;;)
        {
            if (finished)
            {
                Console.WriteLine($"result = {result}");
                return;
            }
        }
    }
}

hozza létre a kimenetet:

result = 143

Ebben a példában a metódus Main elindít egy új szálat, amely a metódust Thread2futtatja. Ez a metódus egy értéket egy nem illékony, úgynevezett resultmezőbe tárol, majd az illékony mezőben truetároljafinished. A főszál megvárja, amíg a mező finished be van állítva true, majd beolvassa a mezőt result. A deklarálás finishedóta volatile a fő szálnak be kell olvasnia a mező 143értékét result . Ha a mezőt finished nem deklaráltákvolatile, akkor megengedett, hogy result az áruház látható legyen a főszálon , és így a főszál beolvassa a mezőből finisheda 0 értéket . A mezőként finished való deklarálás volatile megakadályozza az ilyen inkonzisztencia kialakulását.

záró példa

15.5.5 Mező inicializálása

A mező kezdeti értéke , legyen szó statikus vagy példánymezőről, a mező típusának alapértelmezett értéke (9.3. §). Az alapértelmezett inicializálás előtt nem lehet megfigyelni egy mező értékét, és így a mező soha nem lesz "nem inicializálva".

Példa: A példa

class Test
{
    static bool b;
    int i;

    static void Main()
    {
        Test t = new Test();
        Console.WriteLine($"b = {b}, i = {t.i}");
    }
}

a kimenetet hozza létre

b = False, i = 0

b mert i mindkettő automatikusan inicializálva van az alapértelmezett értékekre.

záró példa

15.5.6 Változó inicializálók

15.5.6.1 Általános

A meződeklarációk variable_initializer is tartalmazhatnak. Statikus mezők esetén a változó inicializálói az osztály inicializálása során végrehajtott hozzárendelési utasításoknak felelnek meg. A példánymezők esetében a változó inicializálói olyan hozzárendelési utasításoknak felelnek meg, amelyeket az osztály egy példányának létrehozásakor hajtanak végre.

Példa: A példa

class Test
{
    static double x = Math.Sqrt(2.0);
    int i = 100;
    string s = "Hello";

    static void Main()
    {
        Test a = new Test();
        Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}");
    }
}

a kimenetet hozza létre

x = 1.4142135623730951, i = 100, s = Hello

mert a hozzárendelés akkor fordul elő, x amikor a statikus mező inicializálói végrehajtják a feladatokat i , és s a példány mező inicializálói végrehajtják a hozzárendeléseket.

záró példa

A §15.5.5-ben leírt alapértelmezett érték inicializálása minden mezőre kiterjed, beleértve a változó inicializálókkal rendelkező mezőket is. Így az osztály inicializálásakor az adott osztály összes statikus mezője először az alapértelmezett értékre lesz inicializálva, majd a statikus mező inicializálói szöveges sorrendben lesznek végrehajtva. Hasonlóképpen, egy osztály egy példányának létrehozásakor az adott példány összes példánymezője először inicializálva lesz az alapértelmezett értékre, majd a példány mező inicializálói szöveges sorrendben lesznek végrehajtva. Ha ugyanahhoz a típushoz több részleges típusdeklarációban is vannak meződeklarációk, a részek sorrendje nem lesz meghatározva. A mező inicializálói azonban minden résznél sorrendben lesznek végrehajtva.

Lehetséges, hogy a változó inicializálókkal rendelkező statikus mezők az alapértelmezett értékállapotukban figyelhetők meg.

Például: Ez azonban erősen elriasztja a stílust. A példa

class Test
{
    static int a = b + 1;
    static int b = a + 1;

    static void Main()
    {
        Console.WriteLine($"a = {a}, b = {b}");
    }
}

ezt a viselkedést mutatja. Annak ellenére, hogy a körkörös definíciók a és b, a program érvényes. Ez a kimenetet eredményezi

a = 1, b = 2

mert a statikus mezők ab inicializálása 0 (az alapértelmezett érték int) az inicializálók végrehajtása előtt történik. A futtatások a inicializálójának b értéke nulla, és így a inicializálva lesz.1 Amikor a futtatások b inicializálója, az egy értéke már 1meg van adva, és így b inicializálva lesz.2

záró példa

15.5.6.2 Statikus mező inicializálása

Az osztály statikus mezőváltozó-inicializálói olyan hozzárendelések sorozatának felelnek meg, amelyeket a szöveges sorrendben hajtanak végre, amelyben azok megjelennek az osztálydeklarációban (15.5.6.1. §). Egy részleges osztályon belül a "szöveges sorrend" jelentését a 15.5.6.1. Ha az osztályban létezik statikus konstruktor (15.12.§), a statikus mező inicializálóinak végrehajtása közvetlenül a statikus konstruktor végrehajtása előtt történik. Ellenkező esetben a statikus mező inicializálói implementálástól függő időpontban lesznek végrehajtva az adott osztály statikus mezőjének első használata előtt.

Példa: A példa

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    public static int X = Test.F("Init A");
}

class B
{
    public static int Y = Test.F("Init B");
}

a következő kimenetet hozhatja létre:

Init A
Init B
1 1

vagy a kimenet:

Init B
Init A
1 1

mivel az inicializáló és Xaz inicializáló végrehajtása Ybármelyik sorrendben megtörténhet; ezek csak a mezőkre mutató hivatkozások előtt korlátozottak. A példában azonban:

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    static A() {}
    public static int X = Test.F("Init A");
}

class B
{
    static B() {}
    public static int Y = Test.F("Init B");
}

a kimenetnek a következőknek kell lennie:

Init B
Init A
1 1

mivel a statikus konstruktorok végrehajtásának szabályai (a 15.12. §-ban meghatározottak szerint) úgy rendelkeznek, hogy Ba statikus konstruktornak (és így a Bstatikus mező inicializálóinak) a statikus konstruktor és a mező inicializálói előtt Akell futniuk.

záró példa

15.5.6.3 Példánymező inicializálása

Az osztály példánymezőváltozó-inicializálói olyan hozzárendelések sorozatának felelnek meg, amelyeket azonnal végrehajtanak az osztály bármelyik példánykonstruktorának (15.11.3. §) bejegyzésekor. Egy részleges osztályon belül a "szöveges sorrend" jelentését a 15.5.6.1. A változó inicializálóit a rendszer abban a szöveges sorrendben hajtja végre, amelyben azok megjelennek az osztálydeklarációban (15.5.6.1. §). Az osztálypéldány létrehozásának és inicializálásának folyamatát a 15.11.

Egy példánymező változó inicializálója nem hivatkozhat a létrehozott példányra. Ezért fordítási idő hibát jelent a változó inicializálóban való hivatkozásthis, mivel a változó inicializálójának fordítási idő hibája, hogy egy simple_name keresztül hivatkozhat bármely példánytagra.

Példa: Az alábbi kódban

class A
{
    int x = 1;
    int y = x + 1;     // Error, reference to instance member of this
}

a változó inicializálója y fordítási időt eredményez, mert a létrehozott példány egy tagjára hivatkozik.

záró példa

15.6 Módszerek

15.6.1 Általános

A metódus olyan tag, amely egy objektum vagy osztály által végrehajtható számítást vagy műveletet valósít meg. A metódusok method_declarationhasználatával vannak deklarálva:

method_declaration
    : attributes? method_modifiers return_type method_header method_body
    | attributes? ref_method_modifiers ref_kind ref_return_type method_header
      ref_method_body
    ;

method_modifiers
    : method_modifier* 'partial'?
    ;

ref_kind
    : 'ref'
    | 'ref' 'readonly'
    ;

ref_method_modifiers
    : ref_method_modifier*
    ;

method_header
    : member_name '(' parameter_list? ')'
    | member_name type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

method_modifier
    : ref_method_modifier
    | 'async'
    ;

ref_method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

return_type
    : ref_return_type
    | 'void'
    ;

ref_return_type
    : type
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

method_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    | ';'
    ;

ref_method_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Nyelvtani jegyzetek:

  • unsafe_modifier (23.2. §) csak nem biztonságos kódban érhető el (23. §).
  • method_body felismerésekor, ha a null_conditional_invocation_expression és a kifejezés alternatívái is alkalmazhatók, akkor az előbbit kell választani.

Megjegyzés: Az alternatív megoldások átfedése és prioritása itt kizárólag a leíró kényelem érdekében van; a nyelvtani szabályok kidolgozhatók az átfedés megszüntetéséhez. Az ANTLR és más nyelvhelyességi rendszerek ugyanazt a kényelmet biztosítják, így method_body automatikusan rendelkezik a megadott szemantikával. végjegyzet

A method_declaration tartalmazhat egy attribútumkészletet (22. §) és a deklarált hozzáférhetőség egyik megengedett típusát (15.3.6. §), a new (15.3.5. §), static (15.6.3. §), virtual (15.6.4. §), override (15.6.5. §), sealed (15.6.6. §), abstract (15.6.7. §), extern (15.6.8. §) és async (15.14. §). Ezenkívül egy method_declaration, amelyet közvetlenül egy struct_declaration tartalmaz, tartalmazhatja a readonly módosítót (16.4.12. §).

A deklaráció a módosítók érvényes kombinációjával rendelkezik, ha az alábbiak mindegyike igaz:

  • A deklaráció a hozzáférési módosítók érvényes kombinációját tartalmazza (15.3.6. §).
  • A deklaráció nem tartalmazza többször ugyanazt a módosítót.
  • A deklaráció legfeljebb az alábbi módosítók egyikét tartalmazza: static, virtualés override.
  • A deklaráció legfeljebb az alábbi módosítók egyikét tartalmazza: new és override.
  • Ha a deklaráció tartalmazza a abstract módosítót, akkor a deklaráció nem tartalmazza a következő módosítókat: static, virtual, sealedvagy extern.
  • Ha a deklaráció tartalmazza a private módosítót, akkor a deklaráció nem tartalmazza a következő módosítókat: virtual, overridevagy abstract.
  • Ha a deklaráció tartalmazza a sealed módosítót, akkor a deklaráció tartalmazza a override módosítót is.
  • Ha a deklaráció tartalmazza a partial módosítót, akkor nem tartalmazza a következő módosítókat: new, , public, protectedinternal, private, virtual, sealed, , override, , abstract, vagy extern.

A metódusok besorolása a következők szerint történik:

  • Ha ref a metódus jelen van, akkor a metódus hiv-by-ref értéket ad vissza, és egy változóhivatkozást ad vissza, amely opcionálisan írásvédett;
  • Ellenkező esetben, ha return_type, voidakkor a metódus nem értéket ad vissza, és nem ad vissza értéket;
  • Ellenkező esetben a metódus értéket ad vissza , és egy értéket ad vissza.

A visszatérési érték szerinti vagy a visszatérési érték nélküli metódus deklarációjának return_type adja meg a metódus által visszaadott eredmény típusát, ha van ilyen. Csak a visszatérési érték nélküli metódus tartalmazhat módosítót partial (15.6.9. §). Ha a deklaráció tartalmazza a async módosítót, akkor a return_type-nek vagy void-nek kell lennie, vagy a metódus értékszerint tér vissza, és a visszatérési típus egy tevékenységtípus (§15.14.1).

A returns-by-ref metódus deklarációjának ref_return_type határozza meg a metódus által visszaadott variable_reference által hivatkozott változó típusát.

Az általános metódus olyan metódus, amelynek deklarációja type_parameter_list tartalmaz. Ez adja meg a metódus típusparamétereit. Az opcionális type_parameter_constraints_clausemegadják a típusparaméterek korlátozásait.

Az általános method_declaration az interfésztagok explicit implementációja nem rendelkezhet type_parameter_constraints_clause; a deklaráció örökli az interfészmetódusra vonatkozó korlátozásokból eredő korlátozásokat.

Hasonlóképpen, a override módosítóval rendelkező módszerdeklarációnak nem lehet type_parameter_constraints_clauseértéke, és a metódus típusparamétereinek korlátozásai öröklődnek a felülírandó virtuális módszertől.

A member_name adja meg a metódus nevét. Hacsak a módszer nem explicit felületi tag implementáció (18.6.2. §), a member_name egyszerűen egy azonosító.

Az explicit felülettag-implementáció esetében a member_name egy interface_type , majd egy "." és egy azonosítóból áll. Ebben az esetben a nyilatkozat nem tartalmazhat más módosítókat, mint (esetleg) extern vagy async.

Az opcionális parameter_list a metódus paramétereit határozza meg (15.6.2. §).

A return_type vagy ref_return_type, valamint a módszer parameter_list hivatkozott egyes típusainak legalább olyan hozzáférhetőnek kell lenniük, mint maga a módszer (7.5.5. §).

A visszatérési érték szerinti vagy a nem érték típusú metódus method_body pontosvessző, blokktörzs vagy kifejezéstörzs. A blokk törzse egy blokkból áll, amely megadja a metódus meghívásakor végrehajtandó utasításokat. A kifejezéstörzs egy null_conditional_invocation_expression vagy kifejezésből=>egy pontosvesszőből áll, és egyetlen kifejezést jelöl, amelyet a metódus meghívásakor végre kell hajtani.

Az absztrakciós és externálási módszerek esetében a method_body egyszerűen pontosvesszőből áll. Részleges metódusok esetén a method_body pontosvesszőből, blokktörzsből vagy kifejezéstörzsből állhat. Minden más metódus esetében a method_body egy blokktörzs vagy egy kifejezéstörzs.

Ha a method_body pontosvesszőből áll, a deklaráció nem tartalmazza a async módosítót.

A visszatérési metódusok ref_method_body pontosvessző, blokktörzs vagy kifejezéstörzs. A blokk törzse egy blokkból áll, amely megadja a metódus meghívásakor végrehajtandó utasításokat. A kifejezéstörzs egy variable_reference=>ref, és egyetlen variable_reference jelöl a metódus meghívásának kiértékeléséhez.

Absztrakciós és externálási metódusok esetén a ref_method_body egyszerűen pontosvesszőből áll; minden más módszer esetében a ref_method_body blokktörzs vagy kifejezéstörzs.

A metódus aláírását (7.6. §) a metódus neve, típusparaméterek száma és paraméterlistája határozza meg. A metódus aláírása konkrétan a nevéből, típusparamétereinek számából és a parameter_mode_modifier (15.6.2.1. §) és a paraméterek típusaiból áll. A visszatérési típus nem része egy metódus aláírásának, és nem is a paraméterek neve, a típusparaméterek neve vagy a korlátozások. Ha egy paramétertípus a metódus típusparaméterére hivatkozik, a típusparaméter sorszámát (nem a típusparaméter nevét) használja a típusegyenlítéshez.

A módszer neve eltér az ugyanabban az osztályban deklarált összes többi nem metódus nevétől. Ezenkívül egy módszer aláírásának különböznie kell az ugyanabban az osztályban deklarált összes többi módszer aláírásától, és az ugyanabban az osztályban deklarált két módszer nem rendelkezhet olyan aláírással, amely kizárólag inaz , outés ref.

A metódus type_parameterhatóköre az egész method_declaration kiterjed, és a return_type vagy ref_return_type, method_body vagy ref_method_body és type_parameter_constraints_clausetype_parameter_constraints_clause attribútumaiban is használható.

Minden paraméternek és típusparaméternek eltérő nevet kell tartalmaznia.

15.6.2 Metódusparaméterek

15.6.2.1 Általános

A metódus paramétereit , ha vannak ilyenek, a metódus parameter_list deklarálja.

parameter_list
    : fixed_parameters
    | fixed_parameters ',' parameter_array
    | parameter_array
    ;

fixed_parameters
    : fixed_parameter (',' fixed_parameter)*
    ;

fixed_parameter
    : attributes? parameter_modifier? type identifier default_argument?
    ;

default_argument
    : '=' expression
    ;

parameter_modifier
    : parameter_mode_modifier
    | 'this'
    ;

parameter_mode_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

A paraméterlista egy vagy több vesszővel tagolt paraméterből áll, amelyek közül csak az utolsó lehet parameter_array.

A fixed_parameter választható attribútumkészletből (22. §); választható in, out, refvagy this módosító; típusból; azonosítóból és opcionális default_argument áll. Minden fixed_parameter egy adott típusú paramétert deklarál a megadott névvel. A this módosító a metódust bővítménymetódusként jelöli meg, és csak a statikus metódus első paraméterén engedélyezett egy nem általános, nem beágyazott statikus osztályban. Ha a paraméter típus struct vagy típusparaméterstruct, akkor a this módosító kombinálható a módosítóval vagy ref a in módosítóval, de a módosítóval nemout. A bővítménymetelyeket a 15.6.10. A default_argument rendelkező fixed_parameter opcionális paraméternek, míg a default_argument nélküli fixed_parameter kötelező paraméternek nevezzük. A kötelező paraméter nem jelenhet meg egy választható paraméter után egy parameter_list.

Egy , ref vagy módosító paraméter outnem rendelkezhet this. A bemeneti paraméterek default_argument is tartalmazhatnak. A default_argument kifejezésének a következők egyike kell, hogy legyen:

  • constant_expression
  • annak az űrlapnak new S() a kifejezése, amely S értéktípus
  • annak az űrlapnak default(S) a kifejezése, amely S értéktípus

A kifejezésnek implicit módon átalakíthatónak kell lennie egy identitással vagy a paraméter típusára való null értékű átalakítással.

Ha nem kötelező paraméterek lépnek fel egy implementáló részleges metódusdeklarációban (§15.6.9), explicit interfésztag-implementációban (§18.6.2), egyparaméteres indexelő deklarációban (§15.9), vagy operátori deklarációban (§15.10.1) a fordítónak figyelmeztetést kell adnia, mivel ezek a tagok soha nem hívhatók meg oly módon, hogy lehetővé tegyék az argumentumok kihagyására.

A parameter_array választható attribútumkészletből(22. §), params módosítóból, array_type és azonosítóból állnak. A paramétertömb az adott tömbtípus egyetlen paraméterét deklarálja a megadott névvel. A paramétertömb array_type egydimenziós tömbtípusnak kell lennie (17.2. §). Egy metódushívásban a paramétertömb lehetővé teszi az adott tömbtípus egyetlen argumentumának megadását, vagy lehetővé teszi a tömbelemtípus nulla vagy több argumentumának megadását. A paramétertömböket a 15.6.2.4.

Előfordulhat, hogy egy parameter_array egy opcionális paraméter után következik be, de nem rendelkezhet alapértelmezett értékkel – a parameter_array argumentumainak kihagyása ehelyett üres tömb létrehozását eredményezné.

Példa: Az alábbiak különböző paramétereket szemléltetnek:

void M<T>(
    ref int i,
    decimal d,
    bool b = false,
    bool? n = false,
    string s = "Hello",
    object o = null,
    T t = default(T),
    params int[] a
) { }

A parameter_listMi egy kötelező ref paraméter, d egy kötelező értékparaméter, bsoés t választható értékparaméterek, és a paramétertömb.

záró példa

A metódusdeklaráció külön deklarációs területet hoz létre (7.3. §) paraméterek és típusparaméterek számára. A nevet a típusparaméter-lista és a metódus paraméterlistája adja meg ebbe a deklarációs területre. A metódus törzse ( ha van ilyen) ebben a deklarációs térben beágyazottnak minősül. Hiba, hogy egy metódus deklarációs területének két tagja ugyanazt a nevet kapja.

A metódushívás (12.8.10.2. §) létrehozza a metódus paramétereinek és helyi változóinak másolatát, és a meghívás argumentumlistája az újonnan létrehozott paraméterekhez rendel értékeket vagy változóhivatkozásokat. A metódus blokkjában a paraméterekre simple_name kifejezésekben szereplő azonosítóik hivatkozhatnak (12.8.4. §).

A következő paraméterek léteznek:

Megjegyzés: A 7.6. §-ban leírtak szerint a in, out, és ref módosítók a metódus aláírásának részei, de a params módosító nem. végjegyzet

15.6.2.2 Értékparaméterek

A módosítók nélkül deklarált paraméter értékparaméter. Az értékparaméter egy helyi változó, amely a metódushívásban megadott megfelelő argumentumból kapja meg a kezdeti értékét.

A határozott hozzárendelési szabályokkal kapcsolatban lásd a 9.2.5.

A metódushívás megfelelő argumentuma egy implicit módon átalakítható kifejezés (10.2. §) a paramétertípusra.

A metódusok új értékeket rendelhetnek egy értékparaméterhez. Az ilyen hozzárendelések csak az értékparaméter által képviselt helyi tárolóhelyet befolyásolják – nincs hatással a metódushívásban megadott tényleges argumentumra.

15.6.2.3 Referenciaparaméterek

15.6.2.3.1 Általános

A bemeneti, kimeneti és referenciaparaméterek a referenciaparaméterek. A by-reference paraméter egy helyi referenciaváltozó (9.7.§); a kezdeti hivatkozás a metódushívásban megadott megfelelő argumentumból származik.

Megjegyzés: A hivatkozási paraméter hivatkozása a ref-hozzárendelés (= ref) operátorral módosítható.

Ha egy paraméter egy hivatkozási paraméter, a metódushívás megfelelő argumentumának a paraméterrel inrefoutazonos típusú variable_reference (9.5. §) kell állnia. Ha azonban a paraméter paraméter in , az argumentum lehet olyan kifejezés , amelynek implicit konvertálása (10.2. §) létezik az argumentumkifejezésből a megfelelő paraméter típusára.

Az iterátorként (15.15.§) vagy aszinkron függvényként (15.14. §) deklarált függvényeken nem engedélyezettek a referenciaparaméterek.

Több referenciaparamétert használó metódusban több név is képviselheti ugyanazt a tárolási helyet.

15.6.2.3.2 Bemeneti paraméterek

A módosítóval in deklarált paraméter egy bemeneti paraméter. A bemeneti paraméternek megfelelő argumentum vagy a metódushívás helyén meglévő változó, vagy a metódushívás implementációja által létrehozott változó (12.6.2.3. §). A határozott hozzárendelési szabályokkal kapcsolatban lásd a 9.2.8.

Fordítási idő hiba egy bemeneti paraméter értékének módosításakor.

Megjegyzés: A bemeneti paraméterek elsődleges célja a hatékonyság. Ha a metódusparaméter típusa egy nagy szerkezet (a memóriaigény szempontjából), célszerű elkerülni az argumentum teljes értékének másolását a metódus meghívásakor. A bemeneti paraméterek lehetővé teszik, hogy a metódusok a memóriában meglévő értékekre hivatkozhassanak, miközben védelmet nyújtanak az értékek nemkívánatos változásaival szemben. végjegyzet

15.6.2.3.3 Referenciaparaméterek

A módosítóval ref deklarált paraméter referenciaparaméter. A határozott hozzárendelési szabályokkal kapcsolatban lásd a 9.2.6.

Példa: A példa

class Test
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"i = {i}, j = {j}");
    }
}

a kimenetet hozza létre

i = 2, j = 1

Az in meghívásához SwapMainx a következőt jelöli i és y jelöli.j Így a meghívásnak az a hatása, hogy felcseréli az és ija .

záró példa

Példa: Az alábbi kódban

class A
{
    string s;
    void F(ref string a, ref string b)
    {
        s = "One";
        a = "Two";
        b = "Three";
    }

    void G()
    {
        F(ref s, ref s);
    }
}

a meghívás mind a F kettőreG, mind sapedig a .b Ezért a meghíváshoz a nevek sab és mind ugyanarra a tárolóhelyre hivatkoznak, és a három hozzárendelés mind módosítja a példánymezőt.s

záró példa

struct Egy példánymetódusban, példánykiegészítőben (12.2.1. §) vagy konstruktor inicializálóval rendelkező példánykonstruktorban a this kulcsszó pontosan a szerkezettípus referenciaparamétereként viselkedik (12.8.14. §).

15.6.2.3.4 Kimeneti paraméterek

A módosítóval out deklarált paraméter egy kimeneti paraméter. A határozott hozzárendelési szabályokkal kapcsolatban lásd a 9.2.7.

A részleges módszerként deklarált metódus (15.6.9. §) nem rendelkezik kimeneti paraméterekkel.

Megjegyzés: A kimeneti paramétereket általában olyan metódusokban használják, amelyek több visszatérési értéket hoznak létre. végjegyzet

Példa:

class Test
{
    static void SplitPath(string path, out string dir, out string name)
    {
        int i = path.Length;
        while (i > 0)
        {
            char ch = path[i - 1];
            if (ch == '\\' || ch == '/' || ch == ':')
            {
                break;
            }
            i--;
        }
        dir = path.Substring(0, i);
        name = path.Substring(i);
    }

    static void Main()
    {
        string dir, name;
        SplitPath(@"c:\Windows\System\hello.txt", out dir, out name);
        Console.WriteLine(dir);
        Console.WriteLine(name);
    }
}

A példa a kimenetet hozza létre:

c:\Windows\System\
hello.txt

Vegye figyelembe, hogy a változók és dir változók name hozzárendelése nem rendelhető hozzá, mielőtt átadják SplitPathőket, és hogy a hívás után egyértelműen hozzárendeltnek minősülnek.

záró példa

15.6.2.4 Paramétertömbök

A módosítóval params deklarált paraméter egy paramétertömb. Ha egy paraméterlista tartalmaz paramétertömböt, akkor az lesz a lista utolsó paramétere, és egydimenziós tömbtípusúnak kell lennie.

Példa: A paraméterek tömbjének típusaként használható típusok string[]string[][] , de a típus string[,] nem. záró példa

Megjegyzés: A módosító nem kombinálható params a módosítókkal in, outvagy ref. végjegyzet

A paramétertömbök lehetővé teszik az argumentumok megadását egy metódushívás két módszerének egyikében:

  • A paramétertömbhöz megadott argumentum lehet egy olyan kifejezés, amely implicit módon konvertálható (10.2. §) a paramétertömb típusára. Ebben az esetben a paramétertömb pontosan úgy működik, mint egy értékparaméter.
  • Másik lehetőségként a meghívás megadhat nulla vagy több argumentumot a paramétertömbhöz, ahol minden argumentum egy implicit módon konvertálható kifejezés (10.2. §) a paramétertömb elemtípusára. Ebben az esetben a meghívás létrehozza a paramétertömbtípus egy példányát, amelynek hossza megfelel az argumentumok számának, inicializálja a tömbpéldány elemeit a megadott argumentumértékekkel, és az újonnan létrehozott tömbpéldányt használja tényleges argumentumként.

A meghívásban változó számú argumentum engedélyezésének kivételével a paramétertömb pontosan egyenértékű egy azonos típusú értékparaméterrel (15.6.2.2.2. §).

Példa: A példa

class Test
{
    static void F(params int[] args)
    {
        Console.Write($"Array contains {args.Length} elements:");
        foreach (int i in args)
        {
            Console.Write($" {i}");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        int[] arr = {1, 2, 3};
        F(arr);
        F(10, 20, 30, 40);
        F();
    }
}

a kimenetet hozza létre

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

Az első meghívás F egyszerűen átadja a tömböt arr értékparaméterként. Az F második meghívása automatikusan létrehoz egy négy elemet int[] a megadott elemértékekkel, és értékparaméterként átadja a tömbpéldányt. Hasonlóképpen, a harmadik meghívás F nulla elemet int[] hoz létre, és ezt a példányt értékparaméterként adja át. A második és a harmadik meghívás pontosan egyenértékű az íráshoz:

F(new int[] {10, 20, 30, 40});
F(new int[] {});

záró példa

Túlterhelésfeloldás esetén egy paramétertömböt tartalmazó metódus alkalmazható lehet normál vagy kibontott formájában (12.6.4.2. §). A metódus kibontott formája csak akkor érhető el, ha a metódus normál formája nem alkalmazható, és csak akkor, ha a kibontott űrlap aláírásával megegyező aláírású alkalmazható metódus még nem azonos típusú deklarálva van.

Példa: A példa

class Test
{
    static void F(params object[] a) =>
        Console.WriteLine("F(object[])");

    static void F() =>
        Console.WriteLine("F()");

    static void F(object a0, object a1) =>
        Console.WriteLine("F(object,object)");

    static void Main()
    {
        F();
        F(1);
        F(1, 2);
        F(1, 2, 3);
        F(1, 2, 3, 4);
    }
}

a kimenetet hozza létre

F()
F(object[])
F(object,object)
F(object[])
F(object[])

A példában a metódus két lehetséges kibontott formája egy paramétertömbbel már szerepel az osztályban normál metódusként. Ezek a kibontott űrlapok tehát nem tekinthetők túlterhelésfeloldáskor, és az első és harmadik metódushívások így kiválasztják a szokásos módszereket. Ha egy osztály paramétertömböt tartalmazó metódust deklarál, nem ritka, hogy néhány kibontott űrlapot is normál metódusként használ. Ezzel elkerülhető egy olyan tömbpéldány lefoglalása, amely egy paramétertömböt tartalmazó metódus kibővített formájának meghívásakor következik be.

záró példa

A tömb egy referenciatípus, így a paramétertömbhöz átadott érték lehet null.

Példa: A példa:

class Test
{
    static void F(params string[] array) =>
        Console.WriteLine(array == null);

    static void Main()
    {
        F(null);
        F((string) null);
    }
}

hozza létre a kimenetet:

True
False

A második meghívás False egy egyetlen nullhivatkozást tartalmazó tömbnek felel F(new string[] { null }) meg és ad át.

záró példa

Ha egy paramétertömb típusa az object[], lehetséges kétértelműség merül fel a metódus normál formája és az egyetlen object paraméter kibontott formája között. A kétértelműség oka az, hogy egy object[] önmagában implicit módon átalakítható típussá object. A kétértelműség azonban nem jelent problémát, mivel szükség esetén egy stáb beszúrásával megoldható.

Példa: A példa

class Test
{
    static void F(params object[] args)
    {
        foreach (object o in args)
        {
            Console.Write(o.GetType().FullName);
            Console.Write(" ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        object[] a = {1, "Hello", 123.456};
        object o = a;
        F(a);
        F((object)a);
        F(o);
        F((object[])o);
    }
}

a kimenetet hozza létre

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

Az első és az utolsó meghívásnál Fa normál formátum F azért alkalmazható, mert implicit átalakítás létezik az argumentumtípustól a paramétertípusig (mindkettő típus object[]). Így a túlterhelés feloldása kiválasztja a normál formáját F, és az argumentumot normál értékparaméterként adja át. A második és a harmadik meghívás esetén a normál formátum F nem alkalmazható, mert az argumentumtípustól a paramétertípusig nem létezik implicit átalakítás (a típus object nem konvertálható implicit módon típussá object[]). A kiterjesztett formátum F azonban alkalmazható, ezért a túlterhelés feloldása választja ki. Ennek eredményeképpen egy egy elemet object[] hoz létre a hívás, és a tömb egyetlen eleme inicializálódik a megadott argumentumértékkel (amely maga egy hivatkozás egyre object[]).

záró példa

15.6.3 Statikus és példánymetó

Ha egy metódusdeklaráció módosítót static tartalmaz, akkor a metódus statikus módszernek minősül. Ha nincs static módosító, a rendszer azt mondja, hogy a metódus példánymetódus.

A statikus metódusok nem egy adott példányon működnek, és fordítási idő hiba, amelyre statikus metódusban hivatkozni this kell.

A példánymetódus egy osztály egy adott példányán működik, és ez a példány a (this. §) néven érhető el.

A statikus és a példánytagok közötti különbségeket a 15.3.8.

15.6.4 Virtuális módszerek

Ha egy példánymetódus-deklaráció tartalmaz egy virtuális módosítót, a rendszer azt mondja, hogy ez a metódus virtuális módszer. Ha nincs jelen virtuális módosító, a metódus nem virtuális metódusnak minősül.

A nem virtuális metódus implementálása invariáns: Az implementáció ugyanaz, mint a metódus meghívása annak az osztálynak egy példányán, amelyben deklarálva van, vagy egy származtatott osztály egy példánya. Ezzel szemben a virtuális metódus implementációja helyettesíthető származtatott osztályokkal. Az örökölt virtuális módszer implementálásának felülírásának folyamatát ezt a módszert felül kell bírálni (15.6.5. §).

Egy virtuális metódushívásban annak a példánynak a futásideje , amelyre az adott meghívás történik, meghatározza a meghívandó metódus tényleges implementációját. Nem virtuális metódusok meghívása esetén a példány fordítási idejének típusa a meghatározó tényező. Pontos értelemben, ha egy elnevezett N metódust meghív egy argumentumlistával A egy fordítási idő típusú C és futásidejű R példányon (ahol R vagy C egy osztály származik C), a rendszer a következő módon dolgozza fel az invokációt:

  • Kötési időpontban a rendszer a túlterhelés feloldását Calkalmazza az , Nés Aaz adott metódus M kiválasztásához a deklarált és öröklő Cmetódusok készletéből. Ezt a 12.8.10.2.
  • Ezután futásidőben:
    • Ha M nem virtuális metódus, M akkor a rendszer meghívja.
    • M Ellenkező esetben ez egy virtuális módszer, és a rendszer meghívja MR a legelvezetettebb implementációt.

Az osztályban deklarált vagy öröklődő összes virtuális metódus esetében létezik a metódus legelvezetettebb implementációja az adott osztályra vonatkozóan. A virtuális metódusok M osztályra vonatkozó R legkövetkezettebb implementációját az alábbiak szerint határozzuk meg:

  • Ha R tartalmazza a bevezető virtuális deklarációt M, akkor ez a legelvezetettebb implementáció a M tekintetben R.
  • Ellenkező esetben, ha R a felülbírálást Mtartalmazza , akkor ez a legelvezetettebb implementáció a M tekintetben R.
  • Ellenkező esetben a legelvezetettebb végrehajtás M a közvetlen R alaposztály Mvonatkozásában megegyezik a levezetett végrehajtássalR.

Példa: Az alábbi példa a virtuális és a nem virtuális metódusok közötti különbségeket mutatja be:

class A
{
    public void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public new void F() => Console.WriteLine("B.F");
    public override void G() => Console.WriteLine("B.G");
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        a.F();
        b.F();
        a.G();
        b.G();
    }
}

A példában A egy nem virtuális és egy virtuális metódust FGmutat be. Az osztály egy új, nem virtuális metódust Bvezet be , amely elrejti az örökölt metódust F, és felülbírálja.F A példa a kimenetet hozza létre:

A.F
B.F
B.G
B.G

Figyelje meg, hogy az utasítás a.G() nem .B.GA.G Ennek az az oka, hogy a példány futásidejű típusa (vagyis B) nem a példány fordítási idejének típusa (vagyis A) határozza meg a meghívandó metódus tényleges implementációját.

záró példa

Mivel a metódusok elrejthetik az örökölt metódusokat, egy osztály több, azonos aláírású virtuális metódust is tartalmazhat. Ez nem jelent kétértelműséget, mivel a legelvezetettebb módszer ki nem rejtve van.

Példa: Az alábbi kódban

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

class B : A
{
    public override void F() => Console.WriteLine("B.F");
}

class C : B
{
    public new virtual void F() => Console.WriteLine("C.F");
}

class D : C
{
    public override void F() => Console.WriteLine("D.F");
}

class Test
{
    static void Main()
    {
        D d = new D();
        A a = d;
        B b = d;
        C c = d;
        a.F();
        b.F();
        c.F();
        d.F();
    }
}

az C és D az osztályok két virtuális metódust tartalmaznak ugyanazzal az aláírással: Az, amelyik által A bevezetett és az általuk Cbevezetett. A bevezetett C metódus elrejti az örökölt metódust A. Így a felülbírálási D deklaráció felülbírálja a metódus által Cbevezetett metódust , és nem lehet D felülbírálni az által bevezetett metódust A. A példa a kimenetet hozza létre:

B.F
B.F
D.F
D.F

Vegye figyelembe, hogy a rejtett virtuális metódust úgy lehet meghívni, hogy egy kevésbé származtatott típuson keresztül fér hozzá egy példányhoz D , amelyben a metódus nem rejtett.

záró példa

15.6.5 Felülbírálási módszerek

Ha egy példánymetódus-deklaráció módosítót override tartalmaz, a metódus felülbírálási módszernek minősül. A felülbírálási metódus felülír egy örökölt virtuális metódust ugyanazzal az aláírással. Míg a virtuális módszer deklarációja új módszert vezet be, a felülbírálási módszer deklarációja egy meglévő öröklődő virtuális metódusra specializálódik a módszer új implementációjának biztosításával.

A felülbírálási deklarációval felülbírált metódust az osztályban deklarált felülbírálási. A felülbírált alapmetódus az egyes alaposztályok vizsgálatával határozható meg, kezdve az egyes közvetlen alaposztályok közvetlen alaposztályával MC és az egyes egymást követő közvetlen alaposztályokkal, amíg egy adott alaposztálytípusban legalább egy elérhető metódus található, amelynek aláírása ugyanaz, mint C a típusargumentumok helyettesítése után. A felülbírált alapmetódus megkeresése céljából a metódus akkor tekinthető akadálymentesnek , ha publicaz , ha protectedvan , vagy protected internalha az , vagy ugyanabban a programban deklarálva van internalprivate protected , mint C.

Fordítási idő hiba történik, kivéve, ha az alábbiak mindegyike igaz egy felülbírálási deklaráció esetében:

  • Egy felülre szabott alapmetódus a fent leírtak szerint is elhelyezhető.
  • Pontosan egy ilyen felülírt alapmetódus létezik. Ez a korlátozás csak akkor lép érvénybe, ha az alaposztálytípus egy olyan létrehozott típus, amelyben a típusargumentumok helyettesítése két metódus aláírását azonosvá teszi.
  • A felülbírált alapmetódus egy virtuális, absztrakt vagy felülbírálási módszer. Más szóval a felülírt alapmetódus nem lehet statikus vagy nem virtuális.
  • A felülírt alapmetódus nem lezárt módszer.
  • A felülbírált alapmetódus visszatérési típusa és a felülbírálási módszer között identitáskonverzió van.
  • A felülbírálási deklaráció és a felülbírált alapmetódus ugyanazzal a deklarált akadálymentességgel rendelkezik. Más szóval a felülbírálási deklaráció nem módosíthatja a virtuális módszer akadálymentességét. Ha azonban a felülbírált alapmetódus belső védelem alatt áll, és a felülbírálási deklarációt tartalmazó szerelvénynél eltérő szerelvényben van deklarálva, akkor a felülbírálási deklarált akadálymentesség védelmét kell biztosítani.
  • A felülbírálási deklaráció nem határoz meg type_parameter_constraints_clause. Ehelyett a kényszereket a felülírt alapmetódus örökli. A felülírt metódus típusparamétereit az örökölt kényszer típusargumentumai helyettesíthetik. Ez olyan korlátozásokhoz vezethet, amelyek explicit megadásakor érvénytelenek, például értéktípusok vagy lezárt típusok.

Példa: A következő bemutatja, hogyan működnek a felülíró szabályok az általános osztályok esetében:

abstract class C<T>
{
    public virtual T F() {...}
    public virtual C<T> G() {...}
    public virtual void H(C<T> x) {...}
}

class D : C<string>
{
    public override string F() {...}            // Ok
    public override C<string> G() {...}         // Ok
    public override void H(C<T> x) {...}        // Error, should be C<string>
}

class E<T,U> : C<U>
{
    public override U F() {...}                 // Ok
    public override C<U> G() {...}              // Ok
    public override void H(C<T> x) {...}        // Error, should be C<U>
}

záró példa

A felülbírálási deklaráció egy base_access (12.8.15. §) használatával érheti el a felülbírált alapmetódust.

Példa: Az alábbi kódban

class A
{
    int x;

    public virtual void PrintFields() => Console.WriteLine($"x = {x}");
}

class B : A
{
    int y;

    public override void PrintFields()
    {
        base.PrintFields();
        Console.WriteLine($"y = {y}");
    }
}

az base.PrintFields() invocation in B invokes the PrintFields metódust hívja meg.A A base_access letiltja a virtuális hívási mechanizmust, és egyszerűen nem metódusként kezeli az alapmetódustvirtual . Ha a meghívás B meg lett írva ((A)this).PrintFields(), az rekurzív módon hívja meg a PrintFields deklarált Bmetódust , nem pedig a deklarált metódust A, mivel PrintFields virtuális, és a futási idő típusa ((A)this) az B.

záró példa

Egy metódus csak módosító használatával override bírálhat felül egy másik metódust. Minden más esetben az öröklött metódussal azonos aláírású metódus egyszerűen elrejti az örökölt metódust.

Példa: Az alábbi kódban

class A
{
    public virtual void F() {}
}

class B : A
{
    public virtual void F() {} // Warning, hiding inherited F()
}

a F metódus nem B tartalmaz módosítót override , ezért nem bírálja felül a metódust a F következőben A: . Ehelyett a F metódus B elrejti a metódust, Aés figyelmeztetést jelent, mert a deklaráció nem tartalmaz új módosítót.

záró példa

Példa: Az alábbi kódban

class A
{
    public virtual void F() {}
}

class B : A
{
    private new void F() {} // Hides A.F within body of B
}

class C : B
{
    public override void F() {} // Ok, overrides A.F
}

a F metódus elrejti B a virtuális F metódust, amelytől Aöröklődik. Mivel az új F verzió B privát hozzáféréssel rendelkezik, hatóköre csak az osztály törzsét B tartalmazza, és nem terjed ki a következőre C: . Ezért az in deklarációja F felülbírálhatja az C örökölt elemetF.A

záró példa

15.6.6 Lezárt módszerek

Ha egy példánymetódus-deklaráció módosítót sealed is tartalmaz, a rendszer azt mondja, hogy ez a módszer lezárt módszer. A lezárt metódusok felülírnak egy öröklött virtuális metódust ugyanazzal az aláírással. A zárómetódust a módosítóval override is meg kell jelölni. A módosító használata megakadályozza, sealed hogy egy származtatott osztály tovább felülírja a metódust.

Példa: A példa

class A
{
    public virtual void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public sealed override void F() => Console.WriteLine("B.F");
    public override void G()        => Console.WriteLine("B.G");
}

class C : B
{
    public override void G() => Console.WriteLine("C.G");
}

az osztály B két felülbírálási módszert biztosít: egy olyan metódust F , amely rendelkezik a sealed módosítóval, és egy olyan metódust G , amely nem. BA módosító használata megakadályozza sealed a C további felülsúlyozást F.

záró példa

15.6.7 Absztrakt módszerek

Ha egy példánymetódus-deklaráció módosítót abstract is tartalmaz, a metódus absztrakt metódusnak minősül. Bár az absztrakt metódus implicit módon virtuális módszer is, nem rendelkezhet a módosítóval virtual.

Az absztrakt metódus deklarációja új virtuális módszert vezet be, de nem biztosítja a metódus implementálását. Ehelyett a nem absztrakciós származtatott osztályoknak saját megvalósítást kell biztosítaniuk a módszer felülírásával. Mivel az absztrakt metódus nem biztosít tényleges megvalósítást, az absztrakt metódus metódustörzse egyszerűen pontosvesszőből áll.

Az absztrakt módszer deklarációi csak absztrakt osztályokban engedélyezettek (15.2.2.2.2. §).

Példa: Az alábbi kódban

public abstract class Shape
{
    public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r);
}

public class Box : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r);
}

az Shape osztály egy olyan geometriai alakzatobjektum absztrakt fogalmát határozza meg, amely képes magát festeni. A Paint metódus absztrakt, mert nincs értelmes alapértelmezett implementáció. Az Ellipse osztályok konkrét BoxShape megvalósítások. Mivel ezek az osztályok nem absztraktak, felül kell bírálniuk a metódust Paint , és tényleges megvalósítást kell biztosítaniuk.

záró példa

Egy base_access (12.8.15.§) fordítási idő hibája egy absztrakt metódusra való hivatkozáskor.

Példa: Az alábbi kódban

abstract class A
{
    public abstract void F();
}

class B : A
{
    // Error, base.F is abstract
    public override void F() => base.F();
}

a rendszer fordítási idő hibát jelent a base.F() híváshoz, mert absztrakt metódusra hivatkozik.

záró példa

Egy absztrakt metódus deklarációja felülbírálhat egy virtuális metódust. Ez lehetővé teszi, hogy egy absztrakt osztály kényszerítse a metódus újra implementálását származtatott osztályokban, és elérhetetlenné teszi a metódus eredeti implementációját.

Példa: Az alábbi kódban

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

abstract class B: A
{
    public abstract override void F();
}

class C : B
{
    public override void F() => Console.WriteLine("C.F");
}

az osztály A deklarál egy virtuális metódust, az osztály B absztrakciós módszerrel felülbírálja ezt a metódust, az osztály C pedig felülbírálja az absztrakt metódust, hogy saját implementációt biztosítson.

záró példa

15.6.8 Külső módszerek

Ha egy metódusdeklaráció módosítót extern tartalmaz, a metódus külső metódusnak minősül. A külső metódusok külsőleg vannak implementálva, általában a C# nyelv helyett más nyelvet használnak. Mivel a külső metódus deklarációja nem biztosít tényleges megvalósítást, a külső metódus metódustörzse egyszerűen pontosvesszőből áll. A külső módszer nem lehet általános.

A külső módszerhez való kapcsolódás mechanizmusát implementálás határozza meg.

Példa: Az alábbi példa a módosító és az externDllImport attribútum használatát mutatja be:

class Path
{
    [DllImport("kernel32", SetLastError=true)]
    static extern bool CreateDirectory(string name, SecurityAttribute sa);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool RemoveDirectory(string name);

    [DllImport("kernel32", SetLastError=true)]
    static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool SetCurrentDirectory(string name);
}

záró példa

15.6.9 Részleges módszerek

Ha egy metódusdeklaráció módosítót tartalmazpartial, akkor a metódus részleges módszernek minősül. A részleges módszerek csak részleges típusok tagjaiként deklarálhatók (15.2.7. §), és számos korlátozás vonatkozik ra.

A részleges módszerek meghatározhatók egy típusdeklaráció egyik részében, és implementálhatók egy másikban. A megvalósítás nem kötelező; ha egyetlen rész sem valósítja meg a részleges módszert, a részleges módszer deklarációja és az ahhoz intézett összes hívás el lesz távolítva a részegységek kombinációjából eredő típusdeklarációból.

A részleges módszerek nem határozhatnak meg hozzáférési módosítókat; implicit módon privátak. A visszatérési típusuknak kell lennie void, és paramétereik nem lehetnek kimeneti paraméterek. Az azonosító partial csak akkor ismerhető fel környezetfüggő kulcsszóként (6.4.4.4. §) egy metódusdeklarációban, ha közvetlenül a void kulcsszó előtt jelenik meg. A részleges metódusok nem tudják explicit módon implementálni az interfészmetódusokat.

A részleges módszer deklarációinak két típusa van: Ha a módszerdeklaráció törzse pontosvessző, akkor a deklaráció egy meghatározó részleges módszerdeklaráció. Ha a szerv nem pontosvessző, akkor a nyilatkozat implementáló részleges módszer szerinti deklarációnak minősül. A típusdeklaráció részeiben csak egy meghatározott aláírással rendelkező részleges módszerdeklarációt kell meghatározni, és legfeljebb egy, adott aláírással rendelkező részleges módszerdeklarációt kell végrehajtani. Ha végrehajtási részleges módszerről szóló nyilatkozatot adnak meg, rendelkeznie kell egy megfelelő, meghatározó részleges módszerről szóló nyilatkozatnak, és a nyilatkozatoknak az alábbiakban meghatározottak szerint kell egyeznie:

  • A deklarációknak azonos módosítókkal kell rendelkezniük (bár nem feltétlenül ugyanabban a sorrendben), a metódus nevével, a típusparaméterek számával és a paraméterek számával.
  • A deklarációkban szereplő megfelelő paramétereknek azonos módosítókkal (bár nem feltétlenül ugyanabban a sorrendben) és azonos típusokkal vagy identitáskonverter-típusokkal kell rendelkezniük (modulo különbségek a típusparaméterek neveiben).
  • A deklarációkban szereplő megfelelő típusparamétereknek azonos megkötésekkel kell rendelkezniük (modulo különbségek a típusparaméterek neveiben).

A végrehajtási részleges módszer deklarációja ugyanabban a részben jelenhet meg, mint a megfelelő definiált részleges módszerdeklaráció.

Csak egy definiált részleges metódus vesz részt a túlterhelés feloldásában. Így akár végrehajtási deklarációt ad meg, akár nem, a meghívási kifejezések feloldhatják a részleges módszer meghívását. Mivel a részleges metódusok mindig visszaadnak void, az ilyen meghívási kifejezések mindig kifejezésutasítások lesznek. Továbbá, mivel egy részleges módszer implicit módon privatetörténik, az ilyen utasítások mindig a típusdeklaráció azon részeinek egyikén belül történnek, amelyen belül a részleges metódus deklarálva van.

Megjegyzés: A részleges metódusdefiníciók egyezésének meghatározása és implementálása nem igényel paraméterneveket. Ez a névvel ellátott argumentumok (12.6.2.1. §) használatakor meglepő, bár jól definiált viselkedést eredményezhet. Ha például az egyik fájlban definiálja a részleges metódus deklarációt, egy másik fájlban pedig a részleges metódusdeklarációt M :

// File P1.cs:
partial class P
{
    static partial void M(int x);
}

// File P2.cs:
partial class P
{
    static void Caller() => M(y: 0);
    static partial void M(int y) {}
}

érvénytelen, mivel a meghívás a implementálás argumentumnevét használja, nem pedig a definiált részleges metódusdeklarációt.

végjegyzet

Ha egy részleges típusdeklaráció egyik része sem tartalmaz implementációs deklarációt egy adott részleges módszerhez, az azt kiáltó kifejezéseket egyszerűen eltávolítja a kombinált típusdeklarációból. Így az invocation kifejezésnek, beleértve az alkifejezéseket is, futásidőben nincs hatása. Maga a részleges módszer is el lesz távolítva, és nem lesz tagja a kombinált típusdeklarációnak.

Ha egy végrehajtási deklaráció létezik egy adott részleges módszerhez, a részleges metódusok meghívásai megmaradnak. A részleges módszer a végrehajtási részleges módszer deklarációhoz hasonló módszerdeklarációt eredményez, kivéve a következőket:

  • A partial módosító nem szerepel a fájlban.

  • Az eredményül kapott metódusdeklarációban szereplő attribútumok a meghatározás és a részleges metódus deklarációjának meghatározatlan sorrendben történő végrehajtása kombinált attribútumai. A rendszer nem távolítja el az ismétlődéseket.

  • Az eredményül kapott metódusdeklaráció paramétereinek attribútumai a meghatározás és a végrehajtási részleges módszer deklarációjának megfelelő paraméterek együttes attribútumai meghatározatlan sorrendben. A rendszer nem távolítja el az ismétlődéseket.

Ha egy meghatározott deklarációt adnak meg M, de végrehajtási nyilatkozatot nem, akkor a következő korlátozások érvényesek:

  • Fordítási idejű hiba, ha delegátust hozunk létre M-ből (12.8.17.5. §).

  • Fordítási idő hibát jelent, ha egy név nélküli függvényre hivatkozik M , amely kifejezésfatípussá alakul (8.6.§).

  • A meghívás M részeként előforduló kifejezések nem befolyásolják a meghatározott hozzárendelési állapotot (9.4.§), ami fordítási idejű hibákhoz vezethet.

  • M nem lehet egy alkalmazás belépési pontja (7.1. §).

A részleges metódusok akkor hasznosak, ha lehetővé teszik, hogy a típusdeklaráció egyik része testre szabja egy másik rész viselkedését, például azt, amelyet egy eszköz hoz létre. Vegye figyelembe a következő részleges osztálydeklarációt:

partial class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    partial void OnNameChanging(string newName);
    partial void OnNameChanged();
}

Ha ezt az osztályt más részek nélkül állítja össze, a definiált részleges metódusdeklarációk és azok meghívásai el lesznek távolítva, és az eredményül kapott kombinált osztálydeklaráció egyenértékű lesz a következőkkel:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set => name = value;
    }
}

Tegyük fel azonban, hogy egy másik rész is rendelkezésre áll, amely a részleges módszerek végrehajtási deklarációit tartalmazza:

partial class Customer
{
    partial void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    partial void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

Ezután az eredményként kapott kombinált osztálydeklaráció a következőnek felel meg:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

15.6.10 Kiterjesztési módszerek

Ha egy metódus első paramétere tartalmazza a this módosító értékét, a rendszer azt mondja, hogy ez a metódus bővítménymetódus. A kiterjesztési módszereket csak nem általános, nem beágyazott statikus osztályokban lehet deklarálni. A bővítménymetódus első paramétere korlátozott, az alábbiak szerint:

  • Csak akkor lehet bemeneti paraméter, ha értéktípussal rendelkezik
  • Csak akkor lehet referenciaparaméter, ha értéktípussal rendelkezik, vagy általános típust korlátozott a strukturáláshoz
  • Nem lehet mutatótípus.

Példa: Az alábbi példa egy statikus osztályra, amely két kiterjesztési metódust deklarál:

public static class Extensions
{
    public static int ToInt32(this string s) => Int32.Parse(s);

    public static T[] Slice<T>(this T[] source, int index, int count)
    {
        if (index < 0 || count < 0 || source.Length - index < count)
        {
            throw new ArgumentException();
        }
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

záró példa

A bővítménymetódus egy normál statikus módszer. Ezenkívül, ha a magában foglalja a statikus osztályt a hatókörben, a bővítménymetódus meghívható a példánymetódus meghívási szintaxisával (12.8.10.3. §), és első argumentumként a fogadókifejezést használja.

Példa: A következő program a fent deklarált bővítménymetszeteket használja:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2))
        {
            Console.WriteLine(s.ToInt32());
        }
    }
}

A Slice metódus elérhető a string[], és a ToInt32 metódus elérhető, stringmert bővítménymetódusként deklarálták őket. A program jelentése megegyezik a következővel, szokásos statikus metódushívások használatával:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in Extensions.Slice(strings, 1, 2))
        {
            Console.WriteLine(Extensions.ToInt32(s));
        }
    }
}

záró példa

15.6.11. Módszer törzse

A metódusdeklaráció metódustörzse blokktörzsből, kifejezéstörzsből vagy pontosvesszőből áll.

Az absztrakt és külső módszerdeklarációk nem biztosítanak metódus-implementációt, így a metódustestek egyszerűen pontosvesszőből állnak. Bármely más metódus esetében a metódus törzse egy blokk (13.3. §), amely tartalmazza a metódus meghívásakor végrehajtandó utasításokat.

A metódus az, ha a visszatérési típus voidaz, vagy ha a metódus aszinkron, és a visszatérési típus void («TaskType»). Ellenkező esetben a nem aszinkron metódus tényleges visszatérési típusa a visszatérési típusa, a visszatérési típusú «TaskType»<T>aszinkron metódus tényleges visszatérési típusa (15.14.1. §) pedig a T.

Ha egy metódus tényleges visszatérési típusa és void a metódus blokktörzse van, return a blokkban lévő utasítások (13.10.5. §) nem határoznak meg kifejezést. Ha az üres metódus blokkjának végrehajtása normálisan befejeződik (azaz a vezérlés a metódus törzsének végéről halad), az a metódus egyszerűen visszatér a hívóhoz.

Ha egy metódus tényleges visszatérési típusa és void a metódus kifejezéstörzse van, a kifejezésnek E statement_expression kell lennie, és a törzs pontosan megegyezik az űrlap { E; }blokktörzsével.

A visszatérési érték szerinti metódus (15.6.1. §) esetében a metódus törzsében minden egyes visszatérési utasításnak meg kell adnia egy olyan kifejezést, amely implicit módon átalakítható a tényleges visszatérési típusra.

A visszatérési mód (15.6.1. §) esetében a metódus törzsében minden visszatérési utasításnak meg kell adnia egy olyan kifejezést, amelynek típusa a tényleges visszatérési típusé, és ref-safe-context típusú hívókörnyezettel rendelkezik (9.7.2. §).

A visszatérési érték és a visszatérési ref metódusok esetében a metódus törzsének végpontja nem érhető el. Más szóval a vezérlés nem haladhat le a metódus törzsének végéről.

Példa: Az alábbi kódban

class A
{
    public int F() {} // Error, return value required

    public int G()
    {
        return 1;
    }

    public int H(bool b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    public int I(bool b) => b ? 1 : 0;
}

az értékvisszatérítési F módszer fordítási időt eredményez, mert a vezérlőelem a metódus törzsének végéről áramolhat. A G és H a metódusok helyesek, mert az összes lehetséges végrehajtási útvonal egy visszatérési utasításban végződik, amely egy visszatérési értéket határoz meg. A I módszer helyes, mert a törzse egyenértékű egy olyan blokktal, amelyben csak egyetlen visszatérési utasítás szerepel.

záró példa

15.7 Tulajdonságok

15.7.1 Általános

A tulajdonság olyan tag, amely hozzáférést biztosít egy objektum vagy osztály jellemzőihez. A tulajdonságok közé tartozik például a sztring hossza, a betűtípus mérete, az ablak felirata és az ügyfél neve. A tulajdonságok a mezők természetes kiterjesztései – mindkettő társított típusok nevesített tag, a mezők és tulajdonságok elérésének szintaxisa pedig ugyanaz. A mezőkkel ellentétben azonban a tulajdonságok nem a tárolási helyeket jelölik. Ehelyett a tulajdonságok olyan tartozékokkal rendelkeznek , amelyek meghatározzák az értékek olvasása vagy írása során végrehajtandó utasításokat. A tulajdonságok így mechanizmust biztosítanak a műveletek társítására egy objektum vagy osztály jellemzőinek olvasásával és írásával; továbbá lehetővé teszik az ilyen jellemzők kiszámítását.

A tulajdonságok property_declaration használatával vannak deklarálva:

property_declaration
    : attributes? property_modifier* type member_name property_body
    | attributes? property_modifier* ref_kind type member_name ref_property_body
    ;    

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;
    
property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

ref_property_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

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

A property_declaration tartalmazhat attribútumkészletet (22. §) és a deklarált akadálymentesség bármely megengedett típusát (15.3.6. §), a new (15.3.5. §), static (15.7.2. §), virtual (15.6.4. §, 15.7.6. §), override (15.6.5. §, 15.7.6. §), sealed (15.6.6. §), abstract (15.6.7. §, 15.7.6. §) és extern (15.6.8. §). Ezenkívül egy property_declaration, amelyet közvetlenül egy struct_declaration tartalmaz, tartalmazhatja a readonly módosítót (16.4.11. §).

  • Az első egy nem ref értékű tulajdonságot deklarál. Az értéke típustípussal rendelkezik. Ez a tulajdonság olvasható és/vagy írható lehet.
  • A második egy ref-valued tulajdonságot deklarál. Értéke egy variable_reference (9.5 Ez a tulajdonság csak olvasható.

A property_declaration tartalmazhat attribútumkészletet (22. §) és a deklarált akadálymentesség bármely megengedett típusát (15.3.6. §), a new (15.3.5.§), static (15.7.2. §), virtual (15.6.4. §, 15.7.6. §), override (15.6.5. §, 15.7.6. §), sealed (15.6.6. §), abstract (15.6.7. §, 15.7.6. §) és extern (15.6.8. §) módosítók.

A tulajdonságdeklarációkra ugyanazok a szabályok vonatkoznak, mint a módszerdeklarációkra (15.6. §) a módosítók érvényes kombinációira vonatkozóan.

A member_name (15.6.1.1. §) adja meg a tulajdonság nevét. Hacsak a tulajdonság nem egy explicit felületi tag implementációja, a member_name egyszerűen egy azonosító. Az explicit felülettag-implementáció esetében (18.6.2. §) a member_name egy interface_type , majd egy "." és egy azonosítóból áll.

Az ingatlan típusának legalább olyan akadálymentesnek kell lennie, mint maga az ingatlan (7.5.5. §).

A property_body egy utasítástörzsből vagy egy kifejezéstörzsből állhatnak. A nyilatkozat törzsében accessor_declarations, amelyet "{" és "}" jogkivonatok közé kell tenni, deklarálja az ingatlan tartozékait (15.7.3. §). A kellékek megadják a tulajdonság olvasásához és írásához kapcsolódó végrehajtható utasításokat.

Egy property_body egy kifejezésből => és pontosvesszőből álló E kifejezéstörzs pontosan megegyezik az utasítás törzsével{ get { return E; } }, ezért csak írásvédett tulajdonságok megadására használható, ha a get kiegészítő eredményét egyetlen kifejezés adja meg.

A property_initializer csak automatikusan implementált tulajdonsághoz adható (15.7.4. §), és az ilyen tulajdonságok mögöttes mezőjének inicializálását okozza a kifejezés által megadott értékkel.

A ref_property_body egy utasítástörzsből vagy egy kifejezéstörzsből állhatnak. A nyilatkozat törzsében egy get_accessor_declaration deklarálja az ingatlan lekéréses tartozékát (15.7.3. §). A kiegészítő megadja a tulajdonság olvasásához társított végrehajtható utasításokat.

Egy ref_property_body

Megjegyzés: Annak ellenére, hogy egy tulajdonság elérésének szintaxisa megegyezik egy mező szintaxisával, a tulajdonság nem lesz változóként besorolva. Így nem lehet egy tulajdonságot , vagy in argumentumot outátadni, refkivéve, ha a tulajdonság értéke ref-érték, ezért változóhivatkozást ad vissza (9.7. §). végjegyzet

Ha egy tulajdonságdeklaráció módosítót extern tartalmaz, a rendszer azt mondja, hogy a tulajdonság külső tulajdonság. Mivel egy külső tulajdonságdeklaráció nem biztosít tényleges megvalósítást, a accessor_declarations szereplő accessor_bodymindegyikének pontosvesszővel kell rendelkeznie.

15.7.2 Statikus és példánytulajdonságok

Ha egy tulajdonságdeklaráció módosítót static tartalmaz, a tulajdonság statikus tulajdonságnak minősül. Ha nincs static módosító, a rendszer azt mondja, hogy a tulajdonság példánytulajdonság.

A statikus tulajdonság nincs hozzárendelve egy adott példányhoz, és fordítási idő hiba, amelyre hivatkozni this kell egy statikus tulajdonság tartozékaiban.

Egy példánytulajdonság egy osztály adott példányához van társítva, és ez a példány a tulajdonság tartozékaiban (this.§) érhető el .

A statikus és a példánytagok közötti különbségeket a 15.3.8.

15.7.3 Tartozékok

Megjegyzés: Ez a záradék a tulajdonságokra (15.7.§) és az indexelőkre (15.9.§) egyaránt vonatkozik. A záradék a tulajdonságok szempontjából íródott, amikor az indexelők a tulajdonság/tulajdonságok indexelőit/indexelőit helyettesítik, és a 15.9.2. végjegyzet

A tulajdonság accessor_declarations adja meg a tulajdonság írásához és/vagy olvasásához kapcsolódó végrehajtható utasításokat.

accessor_declarations
    : get_accessor_declaration set_accessor_declaration?
    | set_accessor_declaration get_accessor_declaration?
    ;

get_accessor_declaration
    : attributes? accessor_modifier? 'get' accessor_body
    ;

set_accessor_declaration
    : attributes? accessor_modifier? 'set' accessor_body
    ;

accessor_modifier
    : 'protected'
    | 'internal'
    | 'private'
    | 'protected' 'internal'
    | 'internal' 'protected'
    | 'protected' 'private'
    | 'private' 'protected'
    | 'readonly'        // direct struct members only
    ;

accessor_body
    : block
    | '=>' expression ';'
    | ';' 
    ;

ref_get_accessor_declaration
    : attributes? accessor_modifier? 'get' ref_accessor_body
    ;
    
ref_accessor_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

A accessor_declarations get_accessor_declaration, set_accessor_declaration vagy mindkettőből állnak. Minden kiegészítő deklaráció választható attribútumokból, opcionális accessor_modifier, jogkivonatból get vagy setegy accessor_body áll.

A ref-valued tulajdonság esetében a ref_get_accessor_declaration választható attribútumokból, opcionális accessor_modifier, jogkivonatból getés ref_accessor_body áll.

Az accessor_modifiers használatára a következő korlátozások vonatkoznak:

  • A accessor_modifier nem használhatók interfészben vagy explicit interfésztag-megvalósításban.
  • A accessor_modifierreadonly csak olyan property_declaration vagy indexer_declaration engedélyezett, amelyet közvetlenül egy struct_declaration tartalmaz (16.4.11. §, 16.4.13. §).
  • Olyan tulajdonság vagy indexelő esetében, amely nem override rendelkezik módosítóval, a accessor_modifier csak akkor engedélyezett, ha a tulajdonság vagy az indexelő rendelkezik be- és beállítva tartozékokkal, majd csak az egyik tartozékon engedélyezett.
  • Módosítót tartalmazó override tulajdonság vagy indexelő esetén a tartozéknak meg kell egyeznie a felülbírált tartozék accessor_modifier, ha van ilyen.
  • A accessor_modifier szigorúan korlátozóbb akadálymentességet állapít meg, mint maga az ingatlan vagy az indexelő bejelentett akadálymentessége. A pontosság érdekében:
    • Ha a tulajdonság vagy az indexelő rendelkezik deklarált akadálymentességgelpublic, akkor a accessor_modifier által deklarált akadálymentesség lehet private protected, protected internalvagy internalprotectedprivate.
    • Ha a tulajdonság vagy az indexelő rendelkezik deklarált akadálymentességgelprotected internal, akkor a accessor_modifier által deklarált akadálymentesség lehet private protected, protected privatevagy internalprotectedprivate.
    • Ha a tulajdonság vagy indexelő rendelkezik deklarált akadálymentességgel internal vagy protected, akkor az accessor_modifierdeklarált akadálymentesség vagy private protected.
    • Ha az ingatlan vagy indexelő rendelkezik deklarált akadálymentességgelprivate protected, akkor az accessor_modifier által bejelentett akadálymentességnek kell lennieprivate.
    • Ha a tulajdonság vagy az indexelő rendelkezik deklarált akadálymentességgel private, nem használható accessor_modifier .

abstract A nem újraértékelt tulajdonságok esetében extern minden accessor_body minden megadott tartozékhoz egyszerűen pontosvesszőt kell megadni. A nem absztrakt, nem extern tulajdonság, de nem indexelő is rendelkezhet a accessor_body minden megadott tartozékhoz pontosvesszővel, amely esetben automatikusan megvalósított tulajdonság (15.7.4. §). Az automatikusan megvalósított tulajdonságnak legalább be kell szereznie egy tartozékot. Bármely más, nem absztrakt, nem extern tulajdonság tartozékai esetében a accessor_body a következők:

  • egy blokk , amely meghatározza a megfelelő tartozék meghívásakor végrehajtandó utasításokat; vagy
  • egy kifejezéstörzs, amely egy kifejezésből=>pontosvesszőből áll, és egyetlen olyan kifejezést jelöl, amelyet a megfelelő kiegészítő meghívásakor végre kell hajtani.

A ref_accessor_bodyabstractvonatkozóan és extern újraértékelve egyszerűen pontosvessző. Bármely más, nem absztrakt, nem extern tulajdonság tartozéka esetén a ref_accessor_body a következő:

  • egy blokk , amely meghatározza a lekéréses tartozék meghívásakor végrehajtandó utasításokat; vagy
  • egy kifejezéstörzs, amely a következőből =>állref: egy variable_reference és egy pontosvessző. A változóhivatkozás kiértékelése a beolvasási tartozék meghívásakor történik.

A nem hiv-értékű tulajdonság lekéréses tartozéka egy paraméter nélküli metódusnak felel meg a tulajdonságtípus visszatérési értékével. A hozzárendelés céljának kivételével, ha egy ilyen tulajdonságra egy kifejezés hivatkozik, a rendszer meghívja annak lekéréses tartozékát a tulajdonság értékének kiszámításához (12.2.2. §).

A nem újraértékelt tulajdonsághoz tartozó lekéréses tartozék törzsének meg kell felelnie a 15.6.11. A lekéréses tartozék törzsében található összes return utasításnak tartalmaznia kell egy olyan kifejezést, amely implicit módon átalakítható a tulajdonságtípusra. Továbbá a beszerelt tartozék végpontja nem érhető el.

A ref-valued tulajdonság get kiegészítője egy paraméter nélküli metódusnak felel meg, amelynek visszatérési értéke egy variable_reference egy tulajdonságtípus változója. Ha egy kifejezés hivatkozik egy ilyen tulajdonságra, a rendszer meghívja annak lekéréses tartozékát a tulajdonság variable_reference értékének kiszámításához. Ezt a változóhivatkozást a rendszer a többihez hasonlóan olvasásra használja, vagy nem olvasható variable_reference s esetén a környezet által megkövetelt módon írja meg a hivatkozott változót.

Példa: Az alábbi példa egy ref-valued tulajdonságot mutat be egy hozzárendelés céljaként:

class Program
{
    static int field;
    static ref int Property => ref field;

    static void Main()
    {
        field = 10;
        Console.WriteLine(Property); // Prints 10
        Property = 20;               // This invokes the get accessor, then assigns
                                     // via the resulting variable reference
        Console.WriteLine(field);    // Prints 20
    }
}

záró példa

A ref-valued tulajdonság lekéréses tartozékának törzsének meg kell felelnie a 15.6.11.

A készlet tartozékai egy olyan metódusnak felelnek meg, amelynek a tulajdonságtípus egyetlen értékparamétere és egy void visszatérési típus. A készlet tartozékának implicit paramétere mindig el van nevezve value. Ha egy tulajdonságra egy hozzárendelés céljaként hivatkoznak (12.21.§), vagy operandusként ++–- (12.8.16. §, 12.9.6. §), a rendszer meghívja a készlet tartozékát egy olyan argumentummal, amely az új értéket adja meg (12.21.2. §). A szerelvénytestnek meg kell felelnie a void15.6.11. A megadott tartozéktörzsben lévő visszatérési utasítások nem adhatnak meg kifejezést. Mivel egy készlet-tartozék implicit módon rendelkezik egy paraméterrel, valueez egy fordítási idő hiba egy helyi változó vagy állandó deklaráció esetében egy adott tartozékban, hogy az ilyen nevet kapja.

A be- és beszerelt tartozékok jelenléte vagy hiánya alapján a tulajdonság az alábbiak szerint van besorolva:

  • A get tartozékot és a készlethez tartozó tartozékot tartalmazó tulajdonság írás-olvasási tulajdonságnak minősül.
  • A csak lekéréses tartozékot tartalmazó tulajdonságról azt mondják, hogy írásvédett tulajdonság. Fordítási idő hibája, hogy egy írásvédett tulajdonság a hozzárendelés célja.
  • A csak beállított tartozékot tartalmazó tulajdonság írásvédett tulajdonságnak minősül. A hozzárendelés céljaként szolgáló hozzárendelés kivételével fordítási időhiba, amely egy kifejezés írásvédett tulajdonságára hivatkozik.

Megjegyzés: Az elő- és utótag ++-- , valamint az operátorok és az összetett hozzárendelési operátorok nem alkalmazhatók csak írási tulajdonságokra, mivel ezek az operátorok beolvassák az operandus régi értékét, mielőtt megírták az újat. végjegyzet

Példa: Az alábbi kódban

public class Button : Control
{
    private string caption;

    public string Caption
    {
        get => caption;
        set
        {
            if (caption != value)
            {
                caption = value;
                Repaint();
            }
        }
    }

    public override void Paint(Graphics g, Rectangle r)
    {
        // Painting code goes here
    }
}

az Button ellenőrzés köztulajdont Caption deklarál. A Caption tulajdonság lekéréses tartozéka a string privát caption mezőben tárolt értéket adja vissza. A beállított tartozék ellenőrzi, hogy az új érték eltér-e az aktuális értéktől, és ha igen, tárolja az új értéket, és újrafesti a vezérlőt. A tulajdonságok gyakran a fent látható mintát követik: A get kiegészítő egyszerűen egy mezőben tárolt értéket ad private vissza, és a készlet tartozéka módosítja ezt private a mezőt, majd végrehajtja az objektum teljes állapotának frissítéséhez szükséges további műveleteket. Button A fenti osztály alapján a következő példa a tulajdonság használatáraCaption:

Button okButton = new Button();
okButton.Caption = "OK"; // Invokes set accessor
string s = okButton.Caption; // Invokes get accessor

Itt a rendszer meghívja a beállított tartozékot úgy, hogy egy értéket rendel hozzá a tulajdonsághoz, a lekéréses tartozék pedig egy kifejezésben hivatkozik a tulajdonságra.

záró példa

A tulajdonság lekéréses és beállítási tartozékai nem különálló tagok, és nem lehet külön deklarálni egy tulajdonság tartozékait.

Példa: A példa

class A
{
    private string name;

    // Error, duplicate member name
    public string Name
    { 
        get => name;
    }

    // Error, duplicate member name
    public string Name
    { 
        set => name = value;
    }
}

nem deklarál egyetlen írási-olvasási tulajdonságot. Ehelyett két azonos nevű, egy írásvédett és egy írásvédett tulajdonságot deklarál. Mivel az ugyanabban az osztályban deklarált két tag neve nem lehet azonos, a példa fordítási idő hibát okoz.

záró példa

Ha egy származtatott osztály az örökölt tulajdonsággal azonos néven deklarál egy tulajdonságot, a származtatott tulajdonság elrejti az örökölt tulajdonságot mind olvasás, mind írás szempontjából.

Példa: Az alábbi kódban

class A
{
    public int P
    {
        set {...}
    }
}

class B : A
{
    public new int P
    {
        get {...}
    }
}

az P in tulajdonság B elrejti a tulajdonságot PA mind az olvasás, mind az írás szempontjából. Így az utasításokban

B b = new B();
b.P = 1;       // Error, B.P is read-only
((A)b).P = 1;  // Ok, reference to A.P

a hozzárendelés b.P fordítási idő hibát okoz, mivel a csak P olvasható tulajdonság elrejti a fájlban B lévő írásvédett P tulajdonságot A. Vegye figyelembe azonban, hogy a rejtett P tulajdonság eléréséhez egy öntvény használható.

záró példa

A nyilvános mezőktől eltérően a tulajdonságok különválasztják az objektum belső állapotát és a nyilvános felületét.

Példa: Fontolja meg a következő kódot, amely egy Point strukturát használ egy hely ábrázolásához:

class Label
{
    private int x, y;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.x = x;
        this.y = y;
        this.caption = caption;
    }

    public int X => x;
    public int Y => y;
    public Point Location => new Point(x, y);
    public string Caption => caption;
}

Itt az Label osztály két int mezőt használ, x és ya helyét tárolja. A hely nyilvánosan elérhető mind tulajdonságként XY , mind típustulajdonságként LocationPoint. Ha egy későbbi verzióban Labelkényelmesebbé válik a hely Point belső tárolása, a módosítás az osztály nyilvános felületének befolyásolása nélkül végezhető el:

class Label
{
    private Point location;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.location = new Point(x, y);
        this.caption = caption;
    }

    public int X => location.X;
    public int Y => location.Y;
    public Point Location => location;
    public string Caption => caption;
}

x Ha y mezők lettek public readonly volna, akkor lehetetlen lett volna ilyen módosítást végezni az Label osztályon.

záró példa

Megjegyzés: Az állapot tulajdonságokon keresztüli felfedése nem feltétlenül kevésbé hatékony, mint a mezők közvetlen felfedése. Különösen akkor, ha egy tulajdonság nem virtuális, és csak kis mennyiségű kódot tartalmaz, a végrehajtási környezet lecserélheti a kiegészítők hívásait a tartozék tényleges kódjára. Ez a folyamat inlining néven ismert, és olyan hatékonysá teszi a tulajdonsághozzáférést, mint a mezőhozzáférés, de megőrzi a tulajdonságok nagyobb rugalmasságát. végjegyzet

Példa: Mivel a get kiegészítő meghívása elméletileg egyenértékű egy mező értékének olvasásával, rossz programozási stílusnak számít, ha a tartozékok megfigyelhető mellékhatásokat okoznak. A példában

class Counter
{
    private int next;

    public int Next => next++;
}

a tulajdonság értéke attól Next függ, hogy a tulajdonságot hány alkalommal érték el korábban. Így a tulajdonság elérése megfigyelhető mellékhatást eredményez, és a tulajdonságot inkább metódusként kell implementálni.

A "nincs mellékhatás" konvenció a tartozék beszerzéséhez nem jelenti azt, hogy a tartozékokat mindig csak a mezőkben tárolt értékek visszaadására kell írni. A tartozékokat gyakran több mező vagy metódus elérésével számítják ki. A megfelelően megtervezett lekéréses tartozék azonban nem hajt végre olyan műveleteket, amelyek megfigyelhető változásokat okoznak az objektum állapotában.

záró példa

A tulajdonságok segítségével késleltetheti az erőforrások inicializálását az első hivatkozás pillanatáig.

Példa:

public class Console
{
    private static TextReader reader;
    private static TextWriter writer;
    private static TextWriter error;

    public static TextReader In
    {
        get
        {
            if (reader == null)
            {
                reader = new StreamReader(Console.OpenStandardInput());
            }
            return reader;
        }
    }

    public static TextWriter Out
    {
        get
        {
            if (writer == null)
            {
                writer = new StreamWriter(Console.OpenStandardOutput());
            }
            return writer;
        }
    }

    public static TextWriter Error
    {
        get
        {
            if (error == null)
            {
                error = new StreamWriter(Console.OpenStandardError());
            }
            return error;
        }
    }
...
}

Az Console osztály három tulajdonságot tartalmaz, InOutamelyek Errora szabványos bemenetet, kimenetet és hibaeszközöket jelölik. Ha tulajdonságokként teszi ki ezeket a tagokat, az osztály késleltetheti az Console inicializálást, amíg azok ténylegesen használatba nem kerülnek. Például, amikor először hivatkozik a Out tulajdonságra,

Console.Out.WriteLine("hello, world");

létrejön a kimeneti eszköz alapjául TextWriter szolgáló eszköz. Ha azonban az alkalmazás nem hivatkozik a In tulajdonságokra, Error akkor ezekhez az eszközökhöz nem jön létre objektum.

záró példa

15.7.4 Automatikusan implementált tulajdonságok

Az automatikusan implementált tulajdonság (röviden automatikus tulajdonság) egy nem absztrakt, nem extern, nem ref-valued tulajdonság pontosvesszővel csak pontosvesszővel accessor_bodys. Az automatikus tulajdonságoknak rendelkezniük kell egy tartozékgal, és opcionálisan rendelkezniük kell egy készlet tartozékokkal.

Ha egy tulajdonság automatikusan implementált tulajdonságként van megadva, a tulajdonsághoz automatikusan elérhetővé válik egy rejtett háttérmező, a kiegészítők pedig a háttérmezőből való olvasáshoz és íráshoz lesznek implementálva. A rejtett háttérmező nem érhető el, csak az automatikusan implementált tulajdonságkiegészítőken keresztül olvasható és írható, még az azt tartalmazó típuson belül is. Ha az automatikus tulajdonságnak nincs beállított tartozéka, a háttérmezőt kell figyelembe venni readonly (15.5.3. §). A mezőhöz hasonlóan readonly egy írásvédett automatikus tulajdonság is hozzárendelhető a befoglaló osztály egyik konstruktorának törzséhez. Az ilyen hozzárendelés közvetlenül a tulajdonság írásvédett háttérmezőjére van hozzárendelve.

Az automatikus tulajdonságnak lehet property_initializer is, amelyet közvetlenül a háttérmezőre alkalmaz variable_initializer (17.7. §).

Példa:

public class Point
{
    public int X { get; set; } // Automatically implemented
    public int Y { get; set; } // Automatically implemented
}

egyenértékű a következő deklarációval:

public class Point
{
    private int x;
    private int y;

    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

záró példa

Példa: Az alábbiakban

public class ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }

    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

egyenértékű a következő deklarációval:

public class ReadOnlyPoint
{
    private readonly int __x;
    private readonly int __y;
    public int X { get { return __x; } }
    public int Y { get { return __y; } }

    public ReadOnlyPoint(int x, int y)
    {
        __x = x;
        __y = y;
    }
}

Az írásvédett mező hozzárendelései érvényesek, mert a konstruktoron belül történnek.

záró példa

Bár a háttérmező rejtett, előfordulhat, hogy az automatikusan implementált tulajdonság property_declaration (15.7.1. §) keresztül közvetlenül a mezőre célzott attribútumok vannak alkalmazva.

Példa: Az alábbi kód

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret { get; set; }
}

a fordító által létrehozott háttérmezőre a mezőre célzott attribútum NonSerialized lesz alkalmazva, mintha a kód a következőképpen lett volna megírva:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string _mySecretBackingField;
    public string MySecret
    {
        get { return _mySecretBackingField; }
        set { _mySecretBackingField = value; }
    }
}

záró példa

15.7.5 Kisegítő lehetőségek

Ha egy tartozék rendelkezik accessor_modifier, a tartozék akadálymentességi tartományát (7.5.3. §) a accessor_modifier deklarált akadálymentességével határozzák meg. Ha egy tartozék nem rendelkezik accessor_modifier, a tartozék akadálymentességi tartományát a tulajdonság vagy indexelő deklarált akadálymentessége határozza meg.

A accessor_modifier jelenléte soha nem befolyásolja a tagok keresését (12.5.§) vagy a túlterhelés feloldását (12.6.4. §). A tulajdonság vagy indexelő módosítói mindig meghatározzák, hogy melyik tulajdonsághoz vagy indexelőhöz van kötve, függetlenül a hozzáférés környezetétől.

Ha kiválasztott egy adott nem ref értékű tulajdonságot vagy nem ref értékű indexelőt, a rendszer az érintett tartozék akadálymentességi tartományait használja annak megállapítására, hogy a használat érvényes-e:

  • Ha a használat értéke (12.2.2. §), a lekéréses tartozéknak léteznie kell, és hozzáférhetőnek kell lennie.
  • Ha a használat egyszerű feladat célja (12.21.2. §), a készlet tartozékának léteznie kell, és hozzáférhetőnek kell lennie.
  • Ha a használat az összetett hozzárendelés célja (12.21.4. §), vagy az operátorok céljaként ++-- (12.8.16. §, 12.9.6. §), a lekéréses tartozéknak és a készlet tartozékának is léteznie kell és hozzáférhetőnek kell lennie.

Példa: A következő példában a tulajdonság elrejti a tulajdonságot A.TextB.Text, még olyan környezetekben is, ahol csak a beállított tartozékot hívja meg. Ezzel szemben a tulajdonság B.Count nem érhető el az osztály Mszámára, ezért a rendszer inkább az akadálymentes tulajdonságot A.Count használja.

class A
{
    public string Text
    {
        get => "hello";
        set { }
    }

    public int Count
    {
        get => 5;
        set { }
    }
}

class B : A
{
    private string text = "goodbye";
    private int count = 0;

    public new string Text
    {
        get => text;
        protected set => text = value;
    }

    protected new int Count
    {
        get => count;
        set => count = value;
    }
}

class M
{
    static void Main()
    {
        B b = new B();
        b.Count = 12;       // Calls A.Count set accessor
        int i = b.Count;    // Calls A.Count get accessor
        b.Text = "howdy";   // Error, B.Text set accessor not accessible
        string s = b.Text;  // Calls B.Text get accessor
    }
}

záró példa

Ha egy adott ref-értékű tulajdonságot vagy ref-értékű indexelőt választott ki — legyen szó a használat értékéről, egy egyszerű hozzárendelés céljáról vagy egy összetett hozzárendelés céljáról —, a szóban forgó lekérdező hozzáférő hozzáférhetőségi tartományát használjuk annak megállapítására, hogy a használat érvényes-e.

Az interfész megvalósítására használt tartozék nem rendelkezhet accessor_modifier. Ha csak egy tartozékot használnak egy interfész implementálásához, a másik tartozék accessor_modifier deklarálható:

Példa:

public interface I
{
    string Prop { get; }
}

public class C : I
{
    public string Prop
    {
        get => "April";     // Must not have a modifier here
        internal set {...}  // Ok, because I.Prop has no set accessor
    }
}

záró példa

15.7.6 Virtuális, lezárt, felülbíráló és absztrakt tartozékok

Megjegyzés: Ez a záradék a tulajdonságokra (15.7.§) és az indexelőkre (15.9.§) egyaránt vonatkozik. A záradék a tulajdonságok szempontjából íródott, amikor az indexelők a tulajdonság/tulajdonságok indexelőit/indexelőit helyettesítik, és a 15.9.2. végjegyzet

A virtuális tulajdonság deklarációja azt határozza meg, hogy a tulajdonság tartozékai virtuálisak. A virtual módosító a tulajdonság minden nem magánjellegű tartozékára vonatkozik. Ha egy virtuális tulajdonság tartozéka rendelkezik a privateaccessor_modifier, a magánjellegű tartozék implicit módon nem virtuális.

Az absztrakt tulajdonságdeklaráció azt határozza meg, hogy a tulajdonság tartozékai virtuálisak, de nem biztosítják a tartozékok tényleges megvalósítását. Ehelyett a nem absztrakt származtatott osztályoknak saját megvalósítást kell biztosítaniuk a tartozékokhoz a tulajdonság felülírásával. Mivel egy absztrakt tulajdonságdeklaráció kiegészítője nem biztosít tényleges megvalósítást, a accessor_body egyszerűen pontosvesszőből áll. Az absztrakt tulajdonságnak nem lehet tartozéka private .

A mind a abstractoverride módosítókat tartalmazó tulajdonságdeklaráció azt határozza meg, hogy a tulajdonság absztrakt, és felülbírálja az alaptulajdonságot. Az ilyen tulajdonság tartozékai is absztraktak.

Az absztrakt tulajdonság deklarációi csak absztrakt osztályokban engedélyezettek (15.2.2.2.2. §). Az örökölt virtuális tulajdonság tartozékai felülírhatók egy származtatott osztályban egy irányelvre vonatkozó override tulajdonságdeklarációval. Ezt felülíró tulajdonságdeklarációnak nevezzük. A felülíró tulajdonságdeklaráció nem deklarál új tulajdonságot. Ehelyett egyszerűen egy meglévő virtuális tulajdonság kiegészítőinek implementációit specializálja.

A felülbírálási deklarációnak és a felülbírált alaptulajdonságnak ugyanazzal a deklarált akadálymentességgel kell rendelkeznie. Más szóval a felülbírálási nyilatkozat nem módosíthatja az alaptulajdonság akadálymentességét. Ha azonban a felülbírált alaptulajdonság belső védelem alatt áll, és a felülbírálási deklarációt tartalmazó szerelvénynél eltérő szerelvényben van deklarálva, akkor a felülbírálási deklarált akadálymentesség védelmét kell biztosítani. Ha az örökölt tulajdonságnak csak egyetlen tartozéka van (azaz ha az örökölt tulajdonság írásvédett vagy írásvédett), a felülíró tulajdonság csak ezt a tartozékot foglalja magában. Ha az örökölt tulajdonság mindkét tartozékot tartalmazza (azaz ha az örökölt tulajdonság írás-olvasás), a felülíró tulajdonság egyetlen tartozékot vagy mindkét tartozékot tartalmazhat. A felülírás típusa és az örökölt tulajdonság között identitásátalakításnak kell lennie.

Egy felülíró tulajdonságdeklaráció tartalmazhatja a sealed módosítót. Ennek a módosítónak a használata megakadályozza, hogy egy származtatott osztály tovább felülírja a tulajdonságot. A lezárt ingatlan tartozékait is lezárják.

A deklarálási és meghívási szintaxis különbségei kivételével a virtuális, a lezárt, a felülbíráló és az absztrakt kiegészítő pontosan ugyanúgy viselkedik, mint a virtuális, lezárt, felülbíráló és absztrakt metódusok. A 15.6.4., a 15.6.5. §, a 15.6.6. és a 15.6.7. §-ban leírt szabályok úgy vonatkoznak, mintha a kellékek megfelelő formában lennének:

  • A lekéréses kiegészítő egy paraméter nélküli metódusnak felel meg, amelynek a tulajdonságtípus visszatérési értéke ugyanaz, mint az azt tartalmazó tulajdonság.
  • A készlet tartozékai egy olyan metódusnak felelnek meg, amely a tulajdonságtípus egyetlen értékparaméterével, egy üres visszatérési típussal és ugyanazokkal a módosítókkal rendelkezik, mint a tulajdonságot tartalmazó tulajdonság.

Példa: Az alábbi kódban

abstract class A
{
    int y;

    public virtual int X
    {
        get => 0;
    }

    public virtual int Y
    {
        get => y;
        set => y = value;
    }

    public abstract int Z { get; set; }
}

X egy csak virtuális írásvédett tulajdonság, Y egy virtuális olvasási-írási tulajdonság, és Z egy absztrakt írási-olvasási tulajdonság. Mivel Z absztrakt, az A osztályt is absztraktnak kell minősíteni.

Az alábbiakban A egy származtatott osztály látható:

class B : A
{
    int z;

    public override int X
    {
        get => base.X + 1;
    }

    public override int Y
    {
        set => base.Y = value < 0 ? 0: value;
    }

    public override int Z
    {
        get => z;
        set => z = value;
    }
}

Itt a deklarációkXY, és Z felülírják a tulajdonságdeklarációkat. Minden tulajdonságdeklaráció pontosan egyezik a megfelelő öröklött tulajdonság akadálymentességi módosítóival, típusával és nevével. A get tartozék X és a készlet tartozéka Y az alap kulcsszót használja az örökölt tartozékok eléréséhez. A felülbírálások deklarálása Z mindkét absztrakt tartozékot felülírja – így nincsenek kiemelkedő abstract függvénytagok Ba alkalmazásban, és B nem absztrakt osztálynak minősülhetnek.

záró példa

Ha egy tulajdonságot felülbírálásként deklarálnak, minden felülbírált tartozéknak elérhetőnek kell lennie a felülbíráló kódhoz. Ezenkívül magának a tulajdonságnak vagy az indexelőnek, valamint a tartozéknak a bejelentett akadálymentességnek meg kell egyeznie a felülbírált tag és tartozékokkal.

Példa:

public class B
{
    public virtual int P
    {
        get {...}
        protected set {...}
    }
}

public class D: B
{
    public override int P
    {
        get {...}            // Must not have a modifier here
        protected set {...}  // Must specify protected here
    }
}

záró példa

15.8 Események

15.8.1 Általános

Az esemény olyan tag, amely lehetővé teszi, hogy egy objektum vagy osztály értesítéseket adjon meg. Az ügyfelek eseménykezelők biztosításával csatolhatnak végrehajtható kódot az eseményekhez.

Az események deklarálása event_declarationhasználatával történik:

event_declaration
    : attributes? event_modifier* 'event' type variable_declarators ';'
    | attributes? event_modifier* 'event' type member_name
        '{' event_accessor_declarations '}'
    ;

event_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

event_accessor_declarations
    : add_accessor_declaration remove_accessor_declaration
    | remove_accessor_declaration add_accessor_declaration
    ;

add_accessor_declaration
    : attributes? 'add' block
    ;

remove_accessor_declaration
    : attributes? 'remove' block
    ;

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

A eseménydeklaráció tartalmazhat egy attribútumkészletet (22. §) és a deklarált hozzáférhetőségi szintek bármelyik megengedett típusát (15.3.6. §), a new (15.3.5. §), static (15.6.3. §, 15.8.4. §), virtual (15.6.4. §, 15.8.5. §), override (15.6.5. §, 15.8.5. §), sealed (15.6.6. §), abstract (15.6.7. §, 15.8.5. §) és extern (15.6.8. §) módosítókat. Ezenkívül a struct_declaration által közvetlenül tartalmazott event_declaration is tartalmazhatnak readonly módosító elemet (16.4.12. §).

Az eseménydeklarációkra ugyanazok a szabályok vonatkoznak, mint a módszerdeklarációkra (15.6. §) a módosítók érvényes kombinációira vonatkozóan.

Az eseménynyilatkozat típusa delegate_type (8.2.8. §), és hogy a delegate_type legalább olyan hozzáférhetőnek kell lennie, mint maga az esemény (7.5.5. §).

Az eseménydeklarációk event_accessor_declaration is tartalmazhatnak. Ha azonban nem biztosítják azokat, nem extern és nem absztrakt események esetén a fordító automatikusan ellátja őket (§15.8.2); extern események esetén az accessorok külsőleg vannak biztosítva.

Az event_accessor_declarationkihagyó eseménydeklaráció egy vagy több eseményt határoz meg – egyet az egyes variable_declaratoreseményekhez. Az attribútumok és módosítók az ilyen event_declaration által deklarált összes tagra vonatkoznak.

Fordítási időhiba, hogy egy event_declaration a módosító és a abstract event_accessor_declarationis belefoglalja.

Ha egy eseménydeklaráció módosítót extern tartalmaz, az esemény külső eseménynek minősül. Mivel egy külső eseménydeklaráció nem biztosít tényleges implementációt, hiba, hogy a módosító és a extern event_accessor_declarationis szerepel benne.

Fordítási idő hiba egy variable_initializerabstractvagy external módosító használatával.

Az esemény az és += operátorok bal operandusaként -= használható. Ezek az operátorok arra szolgálnak, hogy eseménykezelőket csatoljanak az eseménykezelőkhöz, vagy eltávolítsák az eseménykezelőket, és az esemény hozzáférési módosítói szabályozzák azokat a környezeteket, amelyekben az ilyen műveletek engedélyezettek.

Az egyetlen olyan művelet, amely az esemény deklarálandó típusán kívül eső kóddal engedélyezett az eseményen, az += és -=a . Ezért bár az ilyen kód hozzáadhat és eltávolíthat kezelőket egy eseményhez, nem tudja közvetlenül beszerezni vagy módosítani az eseménykezelők mögöttes listáját.

Az űrlap x += y egy műveletében vagy x –= y– ha x esemény – a művelet eredménye típussal void (12.21.5. §) rendelkezik (szemben xa hozzárendelés utáni értékkelx, mint más+=, nem eseménytípusokon definiált operátorokkal-=). Ez megakadályozza, hogy a külső kód közvetetten megvizsgálja az esemény mögöttes delegáltját.

Példa: Az alábbi példa bemutatja, hogyan vannak csatolva az eseménykezelők az Button osztály példányaihoz:

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;
}

public class LoginDialog : Form
{
    Button okButton;
    Button cancelButton;

    public LoginDialog()
    {
        okButton = new Button(...);
        okButton.Click += new EventHandler(OkButtonClick);
        cancelButton = new Button(...);
        cancelButton.Click += new EventHandler(CancelButtonClick);
    }

    void OkButtonClick(object sender, EventArgs e)
    {
        // Handle okButton.Click event
    }

    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle cancelButton.Click event
    }
}

Itt a LoginDialog példánykonstruktor két Button példányt hoz létre, és eseménykezelőket csatol az Click eseményekhez.

záró példa

15.8.2 Mezőszerű események

Az esemény deklarációját tartalmazó osztály vagy struktúra programszövegében bizonyos események használhatók, például mezők. Az ilyen módon történő használathoz az esemény nem lehet absztrakt vagy extern, és nem tartalmazhat kifejezetten event_accessor_declarations-t. Az ilyen események bármely olyan környezetben használhatók, amely lehetővé teszi a mezők használatát. A mező tartalmaz egy meghatalmazottat (§20), amely az eseményhez hozzáadott eseménykezelők listájára hivatkozik. Ha nem adtak hozzá eseménykezelőket, a mező tartalmazza a következőt null: .

Példa: Az alábbi kódban

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;

    protected void OnClick(EventArgs e)
    {
        EventHandler handler = Click;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public void Reset() => Click = null;
}

Click az osztályon belüli Button mezőként használatos. Ahogy a példa is mutatja, a mező megvizsgálható, módosítható és használható a delegált meghívási kifejezésekben. Az OnClick osztály metódusa Button "emeli" az eseményt Click . Az esemény felemelésének fogalma pontosan egyenértékű az esemény által képviselt meghatalmazott meghívásával – így nincsenek speciális nyelvi szerkezetek az események emeléséhez. Vegye figyelembe, hogy a meghatalmazott meghívását egy ellenőrzés előzi meg, amely biztosítja, hogy a meghatalmazott nem null értékű legyen, és hogy az ellenőrzés helyi példányon történjen a szál biztonságának biztosítása érdekében.

Az osztály deklarációján Button kívül a Click tag csak a bal oldalon és az +=–= operátorok oldalán használható,

b.Click += new EventHandler(...);

amely hozzáfűz egy meghatalmazottat az Click esemény meghívási listájához, és

Click –= new EventHandler(...);

amely eltávolít egy meghatalmazottat az Click esemény meghívási listájából.

záró példa

Mezőszerű esemény összeállításakor a fordító automatikusan létrehozza a delegált tárolására szolgáló tárolót, és olyan kiegészítőket hoz létre az eseményhez, amelyek eseménykezelőket adnak hozzá vagy távolítanak el a delegált mezőhöz. Az összeadási és eltávolítási műveletek szálbiztosak, és a zárolás (13.13.13.§) egy példányeseményhez System.Type vagy az objektum (12.8.18. §) statikus eseményhez való tartása során is elvégezhetők (de nem kötelezőek).

Megjegyzés: Így az űrlap példányesemény-deklarációja:

class X
{
    public event D Ev;
}

a következőnek megfelelő fordítást kell készíteni:

class X
{
    private D __Ev; // field to hold the delegate

    public event D Ev
    {
        add
        {
            /* Add the delegate in a thread safe way */
        }
        remove
        {
            /* Remove the delegate in a thread safe way */
        }
    }
}

Az osztályon Xbelül a bal oldalon lévő hivatkozások Ev és += operátorok –= a kiegészítők hozzáadásának és eltávolításának meghívását okozzák. Az összes többi hivatkozás Ev a rejtett mezőre __Ev való hivatkozásra lesz lefordítva (12.8.7. §). A "__Ev" név tetszőleges; a rejtett mezőnek bármilyen neve lehet, vagy egyáltalán nincs neve.

végjegyzet

15.8.3 Esemény tartozékai

Megjegyzés: Az eseménydeklarációk általában kihagyják event_accessor_declarations-eket, mint a Button fenti példában. Ezek közé tartozhatnak például, ha az eseményenkénti egy mező tárolási költsége nem elfogadható. Ilyen esetekben az osztály event_accessor_declarationis tartalmazhat, és privát mechanizmust használhat az eseménykezelők listájának tárolására. végjegyzet

Az esemény event_accessor_declarations határozza meg az eseménykezelők hozzáadásához és eltávolításához társított végrehajtható utasításokat.

A kiegészítő deklarációk egy add_accessor_declaration és egy remove_accessor_declaration állnak. Minden kiegészítő deklaráció a jogkivonat hozzáadásából vagy eltávolításából, majd egy blokkból áll. A add_accessor_declaration társított blokk megadja az eseménykezelő hozzáadásakor végrehajtandó utasításokat, a remove_accessor_declaration társított blokk pedig az eseménykezelő eltávolításakor végrehajtandó utasításokat.

Minden add_accessor_declaration és remove_accessor_declaration egy olyan metódusnak felel meg, amely az eseménytípus egyetlen értékparaméterével és egy void visszatérési típussal rendelkezik. Az eseménykiegészítő implicit paramétere neve value. Ha egy eseményt használ egy esemény-hozzárendelésben, a rendszer a megfelelő eseménykiegészítőt használja. Pontosabban, ha a hozzárendelési operátor az += , akkor a bővítményt használja a rendszer, és ha a hozzárendelési operátort –= használja, akkor az eltávolítási tartozék lesz használatban. Mindkét esetben a hozzárendelési operátor jobb operandusa lesz az esemény tartozékának argumentuma. Egy add_accessor_declaration vagy remove_accessor_declaration blokkjának meg kell felelnie a 15.6.9.void Különösen az return ilyen blokkokban lévő utasítások nem adhatnak meg kifejezést.

Mivel az esemény-tartozék implicit módon rendelkezik egy paraméterrel, valuefordítási idő hibát jelent egy olyan helyi változó vagy állandó esetében, amely egy esemény-tartozékban van deklarálva, hogy az ilyen névvel rendelkezzen.

Példa: Az alábbi kódban


class Control : Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();

    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}

    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}

    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}

    // MouseDown event
    public event MouseEventHandler MouseDown
    {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }

    // MouseUp event
    public event MouseEventHandler MouseUp
    {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }

    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args)
    {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
        {
            handler(this, args);
        }
    }
}

az Control osztály egy belső tárolási mechanizmust implementál az eseményekhez. A AddEventHandler metódus hozzárendel egy delegált értéket egy kulccsal, a GetEventHandler metódus visszaadja a kulcshoz jelenleg társított delegáltat, a RemoveEventHandler metódus pedig eltávolít egy delegáltat a megadott esemény eseménykezelőjeként. Feltehetően az alapul szolgáló tárolási mechanizmus úgy lett kialakítva, hogy a null delegált érték kulcshoz való társításának nincs költsége, így a kezeletlen események nem használnak tárolót.

záró példa

15.8.4 Statikus és példányesemények

Ha egy eseménydeklaráció módosítóval static rendelkezik, az esemény statikus eseménynek minősül. Ha nincs static módosító, a rendszer azt mondja, hogy az esemény példányesemény.

A statikus esemény nincs egy adott példányhoz társítva, és fordítási idő hiba, amelyre egy statikus esemény tartozékaiban hivatkozni this kell.

A példányesemény egy osztály egy adott példányához van társítva, és ez a példány az esemény tartozékaiban (this. §) érhető el .

A statikus és a példánytagok közötti különbségeket a 15.3.8.

15.8.5 Virtuális, lezárt, felülbíráló és absztrakt tartozékok

A virtuális esemény deklarációja azt határozza meg, hogy az esemény tartozékai virtuálisak. A virtual módosító az esemény mindkét tartozékára vonatkozik.

Az absztrakt eseménydeklaráció azt határozza meg, hogy az esemény tartozékai virtuálisak, de nem biztosítják a tartozékok tényleges megvalósítását. Ehelyett a nem absztrakt származtatott osztályoknak saját megvalósítást kell biztosítaniuk a kiegészítők számára az esemény felülírásával. Mivel egy absztrakt eseménydeklaráció kiegészítője nem biztosít tényleges megvalósítást, nem nyújt event_accessor_declarations-t.

A módosítókat és abstract a override módosítókat is tartalmazó eseménydeklaráció azt határozza meg, hogy az esemény absztrakt, és felülbírálja az alapesetet. Az ilyen események tartozékai is absztraktak.

Az absztrakt eseménydeklarációk csak absztrakt osztályokban engedélyezettek (15.2.2.2. §).

Az örökölt virtuális események tartozékait felül lehet bírálni egy származtatott osztályban egy módosítót meghatározó override eseménydeklarációval. Ez egy felülíró eseménydeklaráció. A felülíró eseménydeklaráció nem deklarál új eseményt. Ehelyett egyszerűen egy meglévő virtuális esemény kiegészítőinek implementációit specializálja.

A felülíró eseménydeklarációnak pontosan ugyanazokat az akadálymentességi módosítókat és nevet kell megadnia, mint a felülírt eseménynek, identitásátalakítást kell alkalmazni a felülírás típusa és a felülírt esemény típusa között, és a kiegészítő és az eltávolító tartozékot is meg kell adni a deklarációban.

Egy felülíró eseménydeklaráció tartalmazhatja a sealed módosítót. A this módosító használata megakadályozza, hogy egy származtatott osztály tovább felülírja az eseményt. A lezárt esemény tartozékait is lezárják.

Fordítási idő hiba, ha egy felülíró eseménydeklaráció módosítót new tartalmaz.

A deklarálási és meghívási szintaxis különbségei kivételével a virtuális, a lezárt, a felülbíráló és az absztrakt kiegészítő pontosan ugyanúgy viselkedik, mint a virtuális, lezárt, felülbíráló és absztrakt metódusok. A 15.6.4., a 15.6.5. §, a 15.6.6. és a 15.6.7. §-ban leírt szabályok úgy vonatkoznak, mintha a tartozék egy megfelelő forma módszere volna. Minden tartozék egy olyan metódusnak felel meg, amelynek az eseménytípus egyetlen értékparamétere, egy void visszatérési típus és ugyanazok a módosítók, mint az azt tartalmazó esemény.

15.9 Indexelők

15.9.1 Általános

Az indexelők olyan tagok, amelyek lehetővé teszik az objektumok indexeléséhez ugyanúgy, mint egy tömböt. Az indexelők indexer_declarationhasználatával vannak deklarálva:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

indexer_declarator
    : type 'this' '[' parameter_list ']'
    | type interface_type '.' 'this' '[' parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;  

ref_indexer_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

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

Az indexer_declaration tartalmazhat egy attribútumkészletet (22. §) és a hozzáférhetőség bármelyik megengedett típusát (15.3.6. §), a new (15.3.5. §), virtual (15.6.4. §), override (15.6.5. §), sealed (15.6.6. §), abstract (15.6.7. §) és extern (15.6.8. §) módosítók. Ezenkívül a struct_declaration, amely közvetlenül tartalmaz egy indexer_declaration-t, tartalmazhatja a readonly módosítót is (16.4.12. §).

  • Az első egy nem ref értékű indexelőt deklarál. Az értéke típustípussal rendelkezik. Az ilyen típusú indexelő olvasható és/vagy írható lehet.
  • A második egy ref-valued indexelőt deklarál. Értéke egy variable_reference (9.5 Ez a fajta indexelő csak olvasható.

Az indexer_declaration tartalmazhat attribútumkészletet

Az indexelő deklarációkra ugyanazok a szabályok vonatkoznak, mint a módszerdeklarációkra (15.6. §) a módosítók érvényes kombinációi tekintetében, azzal a kivétellel, hogy a static módosító nem engedélyezett az indexelő deklarációban.

Az indexelő-deklaráció típusa határozza meg a deklaráció által bevezetett indexelő elemtípusát.

Megjegyzés: Mivel az indexelőket tömbelem-szerű környezetekben való használatra tervezték, a tömbhöz definiált elemtípust indexelővel is használják. végjegyzet

Hacsak az indexelő nem explicit felülettag-implementáció, a típust a kulcsszó thisköveti. Az explicit felület tag implementációja esetén a típust egy interface_type, egy "" és egy kulcsszó követi..this A többi taggal ellentétben az indexelők nem rendelkeznek felhasználó által definiált névvel.

A parameter_list az indexelő paramétereit adja meg. Az indexelő paraméterlistája megfelel egy metódus paraméterlistájának (15.6.2. §), azzal a kivételével, hogy legalább egy paramétert meg kell adni, és hogy a this, refés out paramétermódosítók nem engedélyezettek.

Az indexelő típusának és a parameter_list hivatkozott típusok mindegyikének legalább olyan hozzáférhetőnek kell lennie, mint maga az indexelő (7.5.5. §).

A indexer_body vagy utasítástörzsből (15.7.1. §) vagy kifejezéstörzsből (15.6.1. §) állhat. Egy utasítás törzsében accessor_declarations, amelyet "{" és "}" jogkivonatok közé kell tenni, deklarálja az indexelő tartozékait (15.7.3. §). A tartozékok az indexelő elemeinek olvasásához és írásához kapcsolódó végrehajtható utasításokat határozzák meg.

Egy indexer_body egy "=>" kifejezésből és egy E pontosvesszőből álló kifejezéstörzs pontosan megegyezik az utasítás törzsével{ get { return E; } }, ezért csak írásvédett indexelők megadására használható, ahol a lekéréses kiegészítő eredményét egyetlen kifejezés adja meg.

A ref_indexer_body egy utasítástörzsből vagy egy kifejezéstörzsből állhatnak. Egy utasítástörzsben egy get_accessor_declaration deklarálja az indexelő lekéréses tartozékát (15.7.3. §). A kiegészítő megadja az indexelő olvasásához kapcsolódó végrehajtható utasításokat.

Egy ref_indexer_body

Megjegyzés: Annak ellenére, hogy az indexelő elem elérésének szintaxisa megegyezik egy tömbelem szintaxisával, az indexelő elem nem lesz változóként besorolva. Így az indexelő elemet nem lehet átengedni , vagy argumentumkéntin, outrefkivéve, ha az indexelőt újraf-értékre értékelik, és ezért egy hivatkozást adnak vissza (9.7. §). végjegyzet

Az indexelő parameter_list határozza meg az indexelő aláírását (7.6. §). Az indexelő aláírása a paraméterek számából és típusaiból áll. A paraméterek elemtípusa és neve nem része az indexelő aláírásának.

Az indexelő aláírásának különböznie kell az azonos osztályban deklarált összes többi indexelő aláírásától.

Ha az indexelő deklarációja módosítót tartalmazextern, az indexelő külső indexelőnek minősül. Mivel egy külső indexelő deklaráció nem biztosít tényleges megvalósítást, a accessor_declarations szereplő accessor_body mindegyikének pontosvesszővel kell rendelkeznie.

Példa: Az alábbi példa egy BitArray olyan osztályt deklarál, amely egy indexelőt implementál a bittömb egyes bitjeinek eléréséhez.

class BitArray
{
    int[] bits;
    int length;

    public BitArray(int length)
    {
        if (length < 0)
        {
            throw new ArgumentException();
        }
        bits = new int[((length - 1) >> 5) + 1];
        this.length = length;
    }

    public int Length => length;

    public bool this[int index]
    {
        get
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            return (bits[index >> 5] & 1 << index) != 0;
        }
        set
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            if (value)
            {
                bits[index >> 5] |= 1 << index;
            }
            else
            {
                bits[index >> 5] &= ~(1 << index);
            }
        }
    }
}

Az osztály egy példánya BitArray lényegesen kevesebb memóriát használ fel, mint egy megfelelő bool[] (mivel az előbbi minden értéke csak egy bitet foglal el az utóbbi bytehelyett), de ugyanazt a műveletet teszi lehetővé, mint egy bool[].

Az alábbi CountPrimes osztály egy BitArray és a klasszikus "szita" algoritmust használ a prímszámok számának kiszámításához 2 és egy adott maximum között:

class CountPrimes
{
    static int Count(int max)
    {
        BitArray flags = new BitArray(max + 1);
        int count = 0;
        for (int i = 2; i <= max; i++)
        {
            if (!flags[i])
            {
                for (int j = i * 2; j <= max; j += i)
                {
                    flags[j] = true;
                }
                count++;
            }
        }
        return count;
    }

    static void Main(string[] args)
    {
        int max = int.Parse(args[0]);
        int count = Count(max);
        Console.WriteLine($"Found {count} primes between 2 and {max}");
    }
}

Vegye figyelembe, hogy az elemek BitArray elérésének szintaxisa pontosan ugyanaz, mint egy bool[].

Az alábbi példa egy 26×10-es rácsosztályt mutat be, amely két paraméterrel rendelkező indexelővel rendelkezik. Az első paraméternek az A–Z tartományban nagybetűs vagy kisbetűsnek kell lennie, a másodiknak pedig egész számnak kell lennie a 0–9 tartományban.

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;
    int[,] cells = new int[NumRows, NumCols];

    public int this[char row, int col]
    {
        get
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            return cells[row - 'A', col];
        }
        set
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException ("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            cells[row - 'A', col] = value;
        }
    }
}

záró példa

15.9.2 Indexelő és tulajdonságkülönbségek

Az indexelők és a tulajdonságok koncepciójukban nagyon hasonlóak, de az alábbi módokon különböznek:

  • A tulajdonságot a neve azonosítja, míg az indexelőt az aláírása azonosítja.
  • A tulajdonság egy simple_name (12.8.4.§) vagy member_access (12.8.7. §) keresztül érhető el, míg az indexelő elem egy element_access keresztül érhető el (12.8.12.3. §).
  • A tulajdonságok lehetnek statikus tagok, míg az indexelők mindig példánytagok.
  • A tulajdonság lekéréses tartozéka egy paraméter nélküli metódusnak felel meg, míg az indexelő lekéréses tartozéka egy olyan metódusnak felel meg, amely ugyanazzal a paraméterlistával rendelkezik, mint az indexelő.
  • A tulajdonságkészlet tartozéka egy olyan metódusnak felel meg, amelynek egyetlen paramétere van elnevezve value, míg az indexelő halmaz tartozéka egy olyan metódusnak felel meg, amelynek ugyanaz a paraméterlistája, mint az indexelőnek, valamint egy további, elnevezett valueparaméternek.
  • Fordítási idő hibája, hogy egy indexelő tartozék egy helyi változót vagy helyi állandót deklarál az indexelő paraméter nevével megegyező néven.
  • Egy felülíró tulajdonságdeklarációban az örökölt tulajdonság a szintaxis base.Phasználatával érhető el, ahol P a tulajdonság neve. Egy felülíró indexelő deklarációban az örökölt indexelő a szintaxissal base[E]érhető el, ahol E a kifejezések vesszővel elválasztott listája található.
  • Az "automatikusan implementált indexelő" fogalma nem létezik. Hiba, ha egy nem absztrakt, nem külső indexelő pontosvesszővel accessor_bodys.

Ezen eltéréseken kívül a 15.7.3., a 15.7.5. és a 15.7.6. pontban meghatározott szabályok az indexelő tartozékra és a tulajdonság tartozékára is vonatkoznak.

A tulajdonság/tulajdonságok indexelőre/indexelőre való cseréje a 15.7.3., a 15.7.5. és a 15.7.6. pont elolvasásakor a meghatározott kifejezésekre is vonatkozik. Pontosabban az írási-írási tulajdonság írásvédett indexelővé, az írásvédett tulajdonságé írásvédett indexelővé, a írásvédett tulajdonság pedig írásvédett indexelővé válik.

15.10 Operátorok

15.10.1 Általános

Az operátor olyan tag, amely meghatározza az osztály példányaira alkalmazható kifejezésoperátor jelentését. Az operátorok operator_declarationhasználatával vannak deklarálva:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
    : '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : '+'  | '-'  | '*'  | '/'  | '%'  | '&' | '|' | '^'  | '<<' 
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

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

Megjegyzés: A logikai tagadás előtagja (12.9.4. §) és a null-megbocsátó operátorok utótagja (12.8.9. §), míg ugyanazt a lexikális jogkivonatot (!) jelöli. Ez utóbbi nem túlterhelhető operátor. végjegyzet

A túlterhelhető operátorok három kategóriába sorolhatók: Nem kötelező operátorok (15.10.2.§), bináris operátorok (15.10.3. §) és konverziós operátorok (15.10.4. §).

A operator_body pontosvessző, blokktörzs (15.6.1. §) vagy kifejezéstörzs (15.6.1. §). A blokk törzse egy blokkból áll, amely meghatározza az operátor meghívásakor végrehajtandó utasításokat. A blokknak meg kell felelnie a 15.6.11. A kifejezés törzse => egy kifejezésből és egy pontosvesszőből áll, és egyetlen kifejezést jelöl, amelyet az operátor meghívásakor végre kell hajtani.

Az operátorok esetében extern a operator_body egyszerűen pontosvesszőből áll. Az összes többi operátor esetében a operator_body egy blokktörzs vagy egy kifejezéstörzs.

Az alábbi szabályok az összes operátor-deklarációra vonatkoznak:

  • Az üzemeltetői nyilatkozatnak tartalmaznia kell egy public és egy static módosító is.
  • Az operátor paramétereinek nem lehetnek más módosítói, mint in.
  • Az operátor aláírása (15.10.2. §, 15.10.3. §, 15.10.4. §) különbözik az ugyanabban az osztályban bejelentett összes többi operátor aláírásától.
  • Az üzemeltetői nyilatkozatban hivatkozott összes típusnak legalább olyan hozzáférhetőnek kell lennie, mint maga az üzemeltető (7.5.5. §).
  • Hiba, hogy ugyanaz a módosító többször is megjelenik egy operátor-deklarációban.

Minden egyes operátorkategória további korlátozásokat ír elő az alábbi alklámokban leírtak szerint.

A többi taghoz hasonlóan az alaposztályban deklarált operátorokat származtatott osztályok öröklik. Mivel az operátor-deklarációk mindig megkövetelik azt az osztályt vagy utasítást, amelyben az operátor az operátor aláírásában való részvételre van deklarálva, nem lehetséges, hogy egy származtatott osztályban deklarált operátor elrejtse az alaposztályban deklarált operátort. Így a new módosító soha nem szükséges, és ezért soha nem engedélyezett egy operátori nyilatkozatban.

A nem naplós és bináris operátorokról a 12.4.

A konverziós operátorokról a 10.5.

15.10.2 Nemáris operátorok

A következő szabályok a nem kötelező operátor-deklarációkra vonatkoznak, amelyek T az operátordeklarációt tartalmazó osztály vagy szerkezet példánytípusát jelölik:

  • Egy unáris +, -, ! (csak logikai tagadás) vagy ~ operátornak egyetlen típusparamétert T kell alkalmaznia, vagy T? bármilyen típust visszaadhat.
  • A nem jegyzőnek ++ vagy -- operátornak egyetlen típusparamétert T kell alkalmaznia, vagy T? ugyanazt a típust vagy az abból származtatott típust kell visszaadni.
  • A nem jegyzőnek true vagy false operátornak egyetlen típusparamétert T kell alkalmaznia, vagy T? a típust boolkell visszaadni.

A nem kötelező operátorok aláírása az operátori jogkivonatból (+, -, !, ~, ++, , --, , vagy truefalse) és az egyetlen paraméter típusából áll. A visszatérési típus nem része egy nem kötelező operátor aláírásának, és nem is a paraméter neve.

Az true és false a nem kötelező operátorok párszintű deklarációt igényelnek. Fordítási időhiba akkor fordul elő, ha egy osztály a másik deklarálása nélkül deklarálja az egyik operátort. Az true operátorokat a false 12.24.

Példa: Az alábbi példa egy operátor++ implementációját és későbbi használatát mutatja be egy egész szám vektorosztályhoz:

public class IntVector
{
    public IntVector(int length) {...}
    public int Length { get { ... } }                      // Read-only property
    public int this[int index] { get { ... } set { ... } } // Read-write indexer

    public static IntVector operator++(IntVector iv)
    {
        IntVector temp = new IntVector(iv.Length);
        for (int i = 0; i < iv.Length; i++)
        {
            temp[i] = iv[i] + 1;
        }
        return temp;
    }
}

class Test
{
    static void Main()
    {
        IntVector iv1 = new IntVector(4); // Vector of 4 x 0
        IntVector iv2;
        iv2 = iv1++;              // iv2 contains 4 x 0, iv1 contains 4 x 1
        iv2 = ++iv1;              // iv2 contains 4 x 2, iv1 contains 4 x 2
    }
}

Figyelje meg, hogy az operátor metódus az 1 operandushoz való hozzáadásával előállított értéket adja vissza, ugyanúgy, mint a postfix növekményes és decrement operátorok (12.8.16. §), valamint az előtag növekményes és decrement operátorai (12.9.6. §). A C++-ral ellentétben ez a módszer nem módosíthatja közvetlenül az operandus értékét, mivel ez sérti a postfix növekmény operátor szabványos szemantikáját (12.8.16. §).

záró példa

15.10.3 Bináris operátorok

A bináris operátor-deklarációkra az alábbi szabályok vonatkoznak, amelyek T az operátordeklarációt tartalmazó osztály vagy szerkezet példánytípusát jelölik:

  • A bináris nem műszakos operátornak két paramétert kell alkalmaznia, amelyek közül legalább az egyik típussal T vagy T?, és bármilyen típust visszaadhat.
  • A bináris << vagy >> operátornak (12.11.) két paramétert kell használnia, amelyek közül az első típus T vagy T?, a második típusnak pedig int vagy int?kell, és bármilyen típust visszaadhat.

A bináris operátor aláírása az operátor jogkivonatából (, , , , +-, *, /, %, &|^<<>>==, !=vagy >) és a két paraméter típusából áll. <>=<= A visszatérési típus és a paraméterek neve nem része a bináris operátor aláírásának.

Egyes bináris operátorok párszintű deklarációt igényelnek. A pár bármelyik operátorának minden deklarációja esetében a pár másik operátorának egyező deklarációját kell megadni. Két operátordeklaráció egyezik, ha identitáskonvertálások léteznek a visszatérési típusok és a hozzájuk tartozó paramétertípusok között. A következő operátorok párszintű deklarációt igényelnek:

  • operátor == és operátor !=
  • operátor > és operátor <
  • operátor >= és operátor <=

15.10.4 Konverziós operátorok

A konverziós operátor deklarációja egy felhasználó által definiált átalakítást vezet be (10.5.§), amely kibővíti az előre definiált implicit és explicit konverziókat.

A kulcsszót tartalmazó implicit konverziós operátor-deklaráció felhasználó által definiált implicit konverziót vezet be. Az implicit konverziók számos helyzetben előfordulhatnak, beleértve a függvénytagok meghívását, a leadott kifejezéseket és a hozzárendeléseket. Ezt a 10.2.

A kulcsszót tartalmazó explicit konverziós operátor-deklaráció felhasználó által definiált explicit konverziót vezet be. Explicit átalakítások történhetnek a öntött kifejezésekben, és a 10.3.

A konverziós operátor az átalakítás operátor paramétertípusa által megjelölt forrástípusból céltípussá alakítja át a konvertálási operátor visszatérési típusával jelzett céltípust.

Ha egy adott forrástípus S és céltípus Tnull S értékű vagy T null értékű, hagyja S₀ és T₀ tekintse meg az alapul szolgáló típusokat, egyébként S₀T₀ pedig egyenlő ST és megfelelő. Egy osztály vagy szerkezet csak akkor deklarálhat egy forrástípusból céltípusra ST való átalakítást, ha az alábbiak mindegyike igaz:

  • S₀ és T₀ különböző típusok.

  • S₀ Vagy T₀ az operátordeklarációt tartalmazó osztály vagy szerkezet példánytípusa.

  • Sem S₀ interface_typeT₀.

  • A felhasználó által definiált konverziók kivételével a konvertálás nem létezik a következőre ST vagy onnan: TS.

E szabályok alkalmazásában a rendszer figyelmen kívül hagyja azokat a típusparamétereket S , amelyek más típusokkal nem rendelkeznek öröklési kapcsolatban, vagy T amelyek egyedi típusnak minősülnek, és figyelmen kívül hagyják az ilyen típusú paraméterekre vonatkozó korlátozásokat.

Példa: Az alábbiakban:

class C<T> {...}

class D<T> : C<T>
{
    public static implicit operator C<int>(D<T> value) {...}     // Ok
    public static implicit operator C<string>(D<T> value) {...}  // Ok
    public static implicit operator C<T>(D<T> value) {...}       // Error
}

az első két operátor-deklaráció azért engedélyezett, mert Tintstringés , illetve egyedi, kapcsolat nélküli típusnak minősülnek. A harmadik operátor azonban hiba, mert C<T> a függvény alaposztálya D<T>.

záró példa

A második szabályból az következik, hogy az átalakítási operátornak át kell alakítania azt az osztályt vagy szerkezettípust, amelyben az operátort deklarálják.

Példa: Osztály- vagy struktúratípus C esetén meg lehet határozni az át- és átállítást CintintC, de nem int a helyről a másikra.bool záró példa

Előre definiált átalakítás közvetlen újradefiniálására nincs lehetőség. Így a konverziós operátorok nem konvertálhatók át az adott típusból vagy object azok közé, mert az implicit és explicit konverziók már léteznek az összes többi típus között object . Hasonlóképpen, sem a forrás, sem a cél típusú átalakítás nem lehet a másik alaptípusa, mivel az átalakítás már létezik. Deklarálhatók azonban olyan általános típusok operátorai, amelyek bizonyos típusargumentumok esetében előre definiált konverzióként már létező konverziókat határoznak meg.

Példa:

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

ha a típus object típusargumentumként Tvan megadva, a második operátor egy már létező átalakítást deklarál (implicit, és ezért explicit konverzió is létezik bármilyen típusból típusobjektummá).

záró példa

Azokban az esetekben, amikor egy előre definiált átalakítás két típus között létezik, a rendszer figyelmen kívül hagyja az ilyen típusok közötti felhasználó által definiált konverziókat. Ezek konkrétan a következők:

  • Ha egy előre definiált implicit átalakítás (10.2. §) típustól S típusig Tlétezik, a rendszer figyelmen kívül hagyja a felhasználó által definiált (implicit vagy explicit) ST konverziókat.
  • Ha egy előre definiált explicit átalakítás (10.3.§) típustól S típusig Tlétezik, a felhasználó által definiált explicit konverziókat ST a rendszer figyelmen kívül hagyja. Továbbá:
    • Ha vagy ST felülettípus, a felhasználó által definiált implicit konverziók ST figyelmen kívül lesznek hagyva.
    • Ellenkező esetben a felhasználó által definiált implicit konverziók ST továbbra is figyelembe lesznek véve.

Az összes típus esetében, de objecta Convertible<T> fenti típus által deklarált operátorok nem ütköznek az előre definiált átalakításokkal.

Példa:

void F(int i, Convertible<int> n)
{
    i = n;                    // Error
    i = (int)n;               // User-defined explicit conversion
    n = i;                    // User-defined implicit conversion
    n = (Convertible<int>)i;  // User-defined implicit conversion
}

Típus esetén objectazonban az előre definiált konverziók minden esetben elrejtik a felhasználó által definiált konverziókat, csak egyet:

void F(object o, Convertible<object> n)
{
    o = n;                       // Pre-defined boxing conversion
    o = (object)n;               // Pre-defined boxing conversion
    n = o;                       // User-defined implicit conversion
    n = (Convertible<object>)o;  // Pre-defined unboxing conversion
}

záró példa

A felhasználó által definiált átalakítások nem konvertálhatók interface_type s-ről vagy interface_type-ra. Ez a korlátozás biztosítja, hogy a interface_type való átalakításkor ne történjen felhasználó által meghatározott átalakítás, és hogy a interface_type konvertálása csak akkor legyen sikeres, ha az átalakítás ténylegesen megvalósítja a megadott object.

A konverziós operátor aláírása a forrástípusból és a céltípusból áll. (Ez az egyetlen olyan tagi forma, amelynek a visszatérési típusa részt vesz az aláírásban.) A konverziós operátor implicit vagy explicit besorolása nem része az operátor aláírásának. Így egy osztály vagy struktúra nem deklarálhat implicit és explicit konverziós operátort is ugyanazzal a forrás- és céltípussal.

Megjegyzés: A felhasználó által definiált implicit konverziókat általában úgy kell megtervezni, hogy soha ne legyenek kivételek, és ne veszítsék el az információkat. Ha egy felhasználó által definiált átalakítás kivételeket okozhat (például azért, mert a forrásargumentum túllépte a tartományt) vagy az információvesztést (például a nagy megrendelésű bitek elvetését), akkor az átalakítást explicit konverzióként kell definiálni. végjegyzet

Példa: Az alábbi kódban

public struct Digit
{
    byte value;

    public Digit(byte value)
    {
        if (value < 0 || value > 9)
        {
            throw new ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d) => d.value;
    public static explicit operator Digit(byte b) => new Digit(b);
}

az átállítás Digitbyte implicit, mert soha nem ad kivételt vagy nem vesz el információt, de az átállítás byteDigit explicit, mivel Digit csak a lehetséges értékek egy részhalmazát bytejelölheti.

záró példa

15.11 Példánykonstruktorok

15.11.1 Általános

A példánykonstruktor egy osztálypéldány inicializálásához szükséges műveleteket megvalósító tag. A példánykonstruktorok constructor_declarationhasználatával vannak deklarálva:

constructor_declaration
    : attributes? constructor_modifier* constructor_declarator constructor_body
    ;

constructor_modifier
    : 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

constructor_declarator
    : identifier '(' parameter_list? ')' constructor_initializer?
    ;

constructor_initializer
    : ':' 'base' '(' argument_list? ')'
    | ':' 'this' '(' argument_list? ')'
    ;

constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

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

A constructor_declaration tartalmazhat attribútumkészletet (22. §), a deklarált akadálymentesség bármely engedélyezett típusát (15.3.6. §) és egy extern (15.6.8. §) módosítót. A konstruktor-deklarációk nem tartalmazhatják többször ugyanazt a módosítót.

A constructor_declarator azonosítójának el kell neveznie azt az osztályt, amelyben a példánykonstruktort deklarálják. Ha más név van megadva, fordítási időhiba lép fel.

A példánykonstruktor választható parameter_list ugyanazok a szabályok vonatkoznak, mint egy metódus parameter_list (15.6. §). Mivel a paraméterek módosítója csak a this kiterjesztési módszerekre vonatkozik (15.6.10. §), a konstruktor parameter_list egyik paramétere sem tartalmazza a this módosítót. A paraméterlista meghatározza a példánykonstruktor aláírását (7.6.§), és szabályozza azt a folyamatot, amelynek során a túlterhelés feloldása (12.6.4. §) kiválaszt egy adott példánykonstruktort egy meghívásban.

A példánykonstruktor parameter_list hivatkozott összes típusnak legalább olyan hozzáférhetőnek kell lennie, mint maga a konstruktor (7.5.5. §).

Az opcionális constructor_initializer egy másik példánykonstruktort határoz meg, amelyet a példánykonstruktor constructor_body megadott utasítások végrehajtása előtt kell meghívni. Ezt a 15.11.2.

Ha egy konstruktor-deklaráció módosítót extern tartalmaz, a konstruktor külső konstruktornak minősül. Mivel egy külső konstruktor-deklaráció nem biztosít tényleges megvalósítást, a constructor_body pontosvesszőből áll. Az összes többi konstruktor esetében a constructor_body

  • egy blokk, amely meghatározza az osztály új példányának inicializálására vonatkozó utasításokat; vagy
  • egy kifejezéstörzs, amely egy kifejezésből=>pontosvesszőből áll, és egyetlen kifejezést jelöl az osztály új példányának inicializálásához.

Az olyan constructor_body, amely blokk vagy kifejezéstörzs, pontosan megfelel egy (void. §).

A példánykonstruktorok nem öröklődnek. Így az osztálynak nincs más példánykonstruktora, mint az osztályban ténylegesen deklaráltak, azzal a kivétellel, hogy ha egy osztály nem tartalmaz példánykonstruktor-deklarációkat, a rendszer automatikusan megadja az alapértelmezett példánykonstruktort (15.11.5. §).

A példánykonstruktorokat object_creation_expressions (12.8.17.2. §) és constructor_initializers hívja meg.

15.11.2 Konstruktor inicializálók

Minden példánykonstruktor (az osztály objectkivételével) implicit módon egy másik példánykonstruktor meghívását is magában foglalja közvetlenül a constructor_body előtt. Az implicit meghíváshoz szükséges konstruktort a constructor_initializer határozza meg:

  • Az űrlap base(argument_list) példánykonstruktor-inicializálója (ahol a argument_list nem kötelező) a közvetlen alaposztály példánykonstruktorának meghívását eredményezi. Ezt a konstruktort argument_list és a 12.6.4. A jelöltpéldány-konstruktorok készlete a közvetlen alaposztály összes akadálymentes példánykonstruktorából áll. Ha ez a készlet üres, vagy ha egyetlen legjobb példánykonstruktor nem azonosítható, fordítási időhiba lép fel.
  • Az űrlap this(argument_list) példánykonstruktor-inicializálója (ahol argument_list nem kötelező) meghív egy másik példánykonstruktort ugyanabból az osztályból. A konstruktort a argument_list és a 12.6.4. A jelölt példánykonstruktorok készlete magában az osztályban deklarált összes példánykonstruktorból áll. Ha az alkalmazandó példánykonstruktorok eredményként kapott készlete üres, vagy ha egyetlen legjobb példánykonstruktor nem azonosítható, fordítási időhiba lép fel. Ha egy példánykonstruktor-deklaráció egy vagy több konstruktor inicializáló láncán keresztül hívja meg magát, fordítási időhiba lép fel.

Ha egy példánykonstruktor nem rendelkezik konstruktor-inicializálóval, az űrlap base() konstruktor-inicializálója implicit módon lesz megadva.

Megjegyzés: Így az űrlap példánykonstruktor-deklarációja

C(...) {...}

pontosan egyenértékű a

C(...) : base() {...}

végjegyzet

A példánykonstruktor-deklaráció parameter_list megadott paraméterek hatóköre magában foglalja a deklaráció konstruktor inicializálóját. Így a konstruktor inicializálója hozzáférhet a konstruktor paramétereihez.

Példa:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y) : base(x + y, x - y) {}
}

záró példa

A példánykonstruktor inicializálója nem fér hozzá a létrehozott példányhoz. Ezért fordítási idő hibát jelent erre hivatkozni a konstruktor inicializálójának argumentumkifejezésében, mivel az argumentumkifejezések fordítási idő hibája, hogy egy simple_name keresztül hivatkoznak bármelyik példánytagra.

15.11.3 Példányváltozó inicializálói

Ha egy nem extern példánykonstruktor nem rendelkezik konstruktor inicializálóval, vagy olyan konstruktor inicializálója van, amelynek formája base(...), a konstruktor implicit módon végrehajtja az osztályában deklarált példánymezők inicializálását a variable_initializers által meghatározottak szerint. Ez a hozzárendelések sorozatának felel meg, amelyeket közvetlenül a konstruktorba való belépéskor és a közvetlen alaposztály-konstruktor implicit meghívása előtt hajtanak végre. A változó inicializálóinak végrehajtása szöveges sorrendben történik, amelyben megjelennek az osztálydeklarációban (15.5.6. §).

A változó inicializálókat nem kell végrehajtaniuk az extern példánykonstruktoroknak.

15.11.4 Konstruktorok végrehajtása

A változó inicializálók hozzárendelési utasításokká alakulnak, és ezek a hozzárendelési utasítások az alaposztálypéldány konstruktorának meghívása előtt lesznek végrehajtva. Ez a rendezés biztosítja, hogy az összes példánymezőt inicializálják a változó inicializálói, mielőtt végrehajtanák az adott példányhoz hozzáféréssel rendelkező utasításokat.

Példa: Tekintettel a következőkre:

class A
{
    public A()
    {
        PrintFields();
    }

    public virtual void PrintFields() {}
}
class B: A
{
    int x = 1;
    int y;

    public B()
    {
        y = -1;
    }

    public override void PrintFields() =>
        Console.WriteLine($"x = {x}, y = {y}");
}

ha új B() példányt használnak B, a következő kimenet jön létre:

x = 1, y = 0

Az érték x 1, mert a változó inicializálója az alaposztálypéldány-konstruktor meghívása előtt lesz végrehajtva. Az érték y azonban 0 (egy alapértelmezett értéke), mert a hozzárendelés int csak az alaposztály-konstruktor visszatérése yután lesz végrehajtva. Érdemes a példányváltozó-inicializálókra és konstruktor inicializálókra úgy gondolni, mint a constructor_body elé automatikusan beszúrt utasításokra. A példa

class A
{
    int x = 1, y = -1, count;

    public A()
    {
        count = 0;
    }

    public A(int n)
    {
        count = n;
    }
}

class B : A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100)
    {
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        max = n;
    }
}

több változó inicializálót tartalmaz; ez is tartalmaz konstruktor inicializálók mindkét formában (base és this). A példa az alábbi kódnak felel meg, ahol minden megjegyzés automatikusan beszúrt utasítást jelez (az automatikusan beszúrt konstruktorhívások szintaxisa nem érvényes, csupán a mechanizmus szemléltetésére szolgál).

class A
{
    int x, y, count;
    public A()
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = 0;
    }

    public A(int n)
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = n;
    }
}

class B : A
{
    double sqrt2;
    ArrayList items;
    int max;
    public B() : this(100)
    {
        B(100);                      // Invoke B(int) constructor
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        sqrt2 = Math.Sqrt(2.0);      // Variable initializer
        items = new ArrayList(100);  // Variable initializer
        A(n - 1);                    // Invoke A(int) constructor
        max = n;
    }
}

záró példa

15.11.5 Alapértelmezett konstruktorok

Ha egy osztály nem tartalmaz példánykonstruktor-deklarációkat, a rendszer automatikusan megadja az alapértelmezett példánykonstruktort. Ez az alapértelmezett konstruktor egyszerűen meghívja a közvetlen alaposztály konstruktorát, mintha az űrlap base()konstruktor inicializálója lenne. Ha az osztály absztrakt, akkor az alapértelmezett konstruktor deklarált akadálymentessége védett. Ellenkező esetben az alapértelmezett konstruktor deklarált akadálymentessége nyilvános.

Megjegyzés: Így az alapértelmezett konstruktor mindig az űrlap

protected C(): base() {}

vagy

public C(): base() {}

ahol C az osztály neve.

végjegyzet

Ha a túlterhelés feloldása nem tudja meghatározni az alaposztály konstruktor-inicializálójának legjobb egyéni jelöltét, fordítási időhiba lép fel.

Példa: Az alábbi kódban

class Message
{
    object sender;
    string text;
}

az alapértelmezett konstruktor azért van megadva, mert az osztály nem tartalmaz példánykonstruktor-deklarációkat. Így a példa pontosan egyenértékű a

class Message
{
    object sender;
    string text;

    public Message() : base() {}
}

záró példa

15.12 Statikus konstruktorok

A statikus konstruktor olyan tag, amely végrehajtja a zárt osztály inicializálásához szükséges műveleteket. A statikus konstruktorok static_constructor_declarationhasználatával vannak deklarálva:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')'
        static_constructor_body
    ;

static_constructor_modifiers
    : 'static'
    | 'static' 'extern' unsafe_modifier?
    | 'static' unsafe_modifier 'extern'?
    | 'extern' 'static' unsafe_modifier?
    | 'extern' unsafe_modifier 'static'
    | unsafe_modifier 'static' 'extern'?
    | unsafe_modifier 'extern' 'static'
    ;

static_constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

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

A static_constructor_declaration tartalmazhat attribútumkészletet (22. §) és módosítót extern (15.6.8. §).

A static_constructor_declaration azonosítójának nevet kell adni annak az osztálynak, amelyben a statikus konstruktor deklarálva van. Ha más név van megadva, fordítási időhiba lép fel.

Ha egy statikus konstruktor-deklaráció módosítóval extern rendelkezik, a statikus konstruktor külső statikus konstruktornak minősül. Mivel egy külső statikus konstruktor-deklaráció nem biztosít tényleges megvalósítást, a static_constructor_body pontosvesszőből áll. Az összes többi statikus konstruktor-deklaráció esetében a static_constructor_body

  • egy blokk, amely meghatározza az osztály inicializálásához végrehajtandó utasításokat; vagy
  • egy kifejezéstörzs, amely egy kifejezésből=>pontosvesszőből áll, és egyetlen végrehajtandó kifejezést jelöl az osztály inicializálásához.

Egy blokk- vagy kifejezéstörzset tartalmazó static_constructor_bodypontosan (void. §).

A statikus konstruktorok nem öröklődnek, és nem hívhatók közvetlenül.

A zárt osztály statikus konstruktora legfeljebb egyszer fut egy adott alkalmazástartományban. A statikus konstruktor végrehajtását a következő események közül az első aktiválja, amely egy alkalmazástartományon belül következik be:

  • Létrejön az osztály egy példánya.
  • A rendszer az osztály bármely statikus tagjára hivatkozik.

Ha egy osztály tartalmazza azt a Main metódust (7.1.§), amelyben a végrehajtás megkezdődik, az osztály statikus konstruktora a Main metódus meghívása előtt hajt végre.

Egy új zárt osztálytípus inicializálásához először létre kell hozni egy új statikus mezőkészletet (15.5.2. §) az adott zárt típushoz. A statikus mezők mindegyikét inicializálni kell az alapértelmezett értékre (15.5.5. §). Ezt követően:

  • Ha nincs statikus konstruktor vagy nem extern statikus konstruktor, akkor:
    • a statikus mező inicializálóit (15.5.6.2. §) ezen statikus mezők esetében kell végrehajtani;
    • akkor a nem extern statikus konstruktort, ha van ilyen, végre kell hajtani.
  • Ellenkező esetben, ha van extern statikus konstruktor, akkor azt végre kell hajtani. A statikus változók inicializálóit nem szükséges külső statikus konstruktorokkal végrehajtani.

Példa: A példa

class Test
{
    static void Main()
    {
        A.F();
        B.F();
    }
}

class A
{
    static A()
    {
        Console.WriteLine("Init A");
    }

    public static void F()
    {
        Console.WriteLine("A.F");
    }
}

class B
{
    static B()
    {
        Console.WriteLine("Init B");
    }

    public static void F()
    {
        Console.WriteLine("B.F");
    }
}

a kimenetnek a következőnek kell lennie:

Init A
A.F
Init B
B.F

mert a statikus konstruktor végrehajtását Aa hívás A.Faktiválja, a statikus konstruktor végrehajtását Bpedig a hívás B.Findítja el.

záró példa

Körkörös függőségek hozhatók létre, amelyek lehetővé teszik a változó inicializálókkal rendelkező statikus mezők alapértelmezett értékállapotban való megfigyelését.

Példa: A példa

class A
{
    public static int X;

    static A()
    {
        X = B.Y + 1;
    }
}

class B
{
    public static int Y = A.X + 1;

    static B() {}

    static void Main()
    {
        Console.WriteLine($"X = {A.X}, Y = {B.Y}");
    }
}

a kimenetet hozza létre

X = 1, Y = 2

A Main metódus végrehajtásához a rendszer először az inicializálót B.Yfuttatja az osztály Bstatikus konstruktora előtt. YAz inicializáló azért futtatja Astatic a konstruktort, mert az értékre A.X hivatkozik. A statikus konstruktor A ennek során kiszámítja az érték, Xés ezzel lekéri az alapértelmezett értéket Y, amely nulla. A.X inicializálása így 1-re történik. A statikus mező inicializálóinak és statikus konstruktorainak futtatásának Afolyamata ezután befejeződik, visszatérve a kezdeti érték Ykiszámításához, amelynek eredménye 2 lesz.

záró példa

Mivel a statikus konstruktort minden bezárt osztálytípus esetében pontosan egyszer hajtja végre a rendszer, kényelmes hely a típusparaméter futásidejű ellenőrzésének kikényszerítésére, amely nem ellenőrizhető fordításkor kényszerekkel (15.2.5. §).

Példa: Az alábbi típus statikus konstruktor használatával kényszeríti ki, hogy a típusargumentum szám legyen:

class Gen<T> where T : struct
{
    static Gen()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
    }
}

záró példa

15.13 Döntősök

Megjegyzés: A specifikáció egy korábbi verziójában az úgynevezett "finalizer" neve "destruktor". A tapasztalatok azt mutatják, hogy a "destruktor" kifejezés zavart okozott, és gyakran helytelen elvárásokat eredményezett, különösen a C++-t ismerő programozók számára. A C++-ban a destruktort determinált módon hívjuk meg, míg a C#-ban a véglegesítő nem. A C#-ból való determinált viselkedéshez a következőt kell használni Dispose: . végjegyzet

A véglegesítő olyan tag, aki végrehajtja az osztálypéldány véglegesítéséhez szükséges műveleteket. A véglegesítő egy finalizer_declaration használatával van deklarálva:

finalizer_declaration
    : attributes? '~' identifier '(' ')' finalizer_body
    | attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
      finalizer_body
    | attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
      finalizer_body
    ;

finalizer_body
    : block
    | '=>' expression ';'
    | ';'
    ;

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

A finalizer_declaration tartalmazhat attribútumokat(22. §).

A finalizer_declarator azonosítójának el kell neveznie azt az osztályt, amelyben a döntőst deklarálják. Ha más név van megadva, fordítási időhiba lép fel.

Ha a véglegesítő deklaráció módosítót tartalmazextern, a véglegesítő külső döntősnek minősül. Mivel egy külső véglegesítő deklaráció nem biztosít tényleges megvalósítást, a finalizer_body pontosvesszőből áll. Az összes többi döntős esetében a finalizer_body

  • egy blokk, amely meghatározza az osztály egy példányának véglegesítéséhez végrehajtandó utasításokat.
  • vagy egy kifejezéstörzset, amely egy kifejezésből=>pontosvesszőből áll, és egyetlen végrehajtandó kifejezést jelöl az osztály egy példányának véglegesítése érdekében.

A blokk- vagy kifejezéstörzset tartalmazó finalizer_body pontosan megfelel egy (void. §).

A véglegesítők nem öröklődnek. Így egy osztálynak nincs más véglegesítője, mint az adott osztályban deklarálható döntősöknek.

Megjegyzés: Mivel a döntősöknek nem kell paraméterekkel rendelkezniük, nem lehet túlterhelni, így egy osztály legfeljebb egy döntőssel rendelkezhet. végjegyzet

A véglegesítők meghívása automatikusan megtörténik, és nem hívhatók meg explicit módon. A példányok akkor lesznek jogosultak a véglegesítésre, ha már nem lehet kódokat használni az adott példányon. A példány véglegesítőjének végrehajtása bármikor megtörténhet, miután a példány jogosulttá válik a véglegesítésre (7.9.§). Egy példány véglegesítésekor az adott példány öröklési láncában lévő véglegesítőket a rendszer sorrendben meghívja a legtöbb származtatotttól a legkevésbé származtatottig. A véglegesítő bármely szálon végrehajtható. A véglegesítő végrehajtásának időpontjára és módjára vonatkozó szabályok további megvitatása: §7.9.

Példa: A példa kimenete

class A
{
    ~A()
    {
        Console.WriteLine("A's finalizer");
    }
}

class B : A
{
    ~B()
    {
        Console.WriteLine("B's finalizer");
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

van

B's finalizer
A's finalizer

mivel az öröklési lánc véglegesítőit sorrendbe rendezik, a legtöbb származtatotttól a legkevésbé származtatottig.

záró példa

A véglegesítők a virtuális metódus Finalize felülírásával implementálódnak a következőn System.Object: . A C#-programok nem bírálhatják felül ezt a metódust, és nem hívhatják meg közvetlenül (vagy felülbírálhatják).

Példa: Például a program

class A
{
    override protected void Finalize() {}  // Error
    public void F()
    {
        this.Finalize();                   // Error
    }
}

két hibát tartalmaz.

záró példa

A fordítónak úgy kell viselkednie, mintha ez a módszer és annak felülbírálása egyáltalán nem létezne.

Példa: Így ez a program:

class A
{
    void Finalize() {}  // Permitted
}

érvényes, és a megjelenített metódus elrejti System.Objecta metódust Finalize .

záró példa

A véglegesítőből kivételt eredményező viselkedésről a 21.4.

15.14 Aszinkron függvények

15.14.1 Általános

A módosítóval rendelkező metódust (15.6. §) vagy névtelen függvényt (12.19. §) aszinkron függvénynekasync. Az aszinkron kifejezés általában minden olyan függvény leírására szolgál, amely rendelkezik a async módosítóval.

Az aszinkron függvény paraméterlistájának fordítási idő hibája bármely , invagy out paraméter vagy bármely típusú paraméter ref megadásakorref struct.

Az aszinkron metódus return_type-ja legyen vagy void, egy feladattípus, vagy egy aszinkron iterátortípus (15.15. §). Az eredményértéket eredményező aszinkron módszer esetében egy tevékenységtípusnak vagy aszinkron iterátortípusnak (15.15.3. §) általánosnak kell lennie. Olyan aszinkron metódus esetében, amely nem hoz létre eredményértéket, a tevékenységtípus nem lehet általános. Az ilyen típusokra ebben a specifikációban «TaskType»<T>«TaskType»az és aként hivatkoznak. A standard kódtártípus System.Threading.Tasks.Task és a System.Threading.Tasks.Task<TResult>System.Threading.Tasks.ValueTask<T> alapján létrehozott típusok feladattípusok, valamint egy osztály-, struktúra- vagy interfésztípus, amely az attribútumon keresztül egy van társítva. Az ilyen típusokra ebben a specifikációban a következőként és néven hivatkozunk «TaskBuilderType»<T> : és «TaskBuilderType». Egy tevékenységtípus legfeljebb egy típusparaméterrel rendelkezhet, és nem ágyazható be általános típusba.

A tevékenységtípust visszaadó aszinkron metódusról azt mondják, hogy feladat-visszaadó.

A tevékenységtípusok pontos definíciójukban eltérőek lehetnek, de a nyelv szempontjából a tevékenységtípusok az egyik állapotban hiányosak, sikeresek vagy hibásak. A hibás tevékenységek egy vonatkozó kivételt rögzítenek. Egy sikeres«TaskType»<T> rekordtípus Teredménye. A feladattípusok várhatók, ezért a feladatok lehetnek a várva várt kifejezések operandusai (12.9.8. §).

Példa: A tevékenységtípus MyTask<T> a feladatszerkesztő típusához MyTaskMethodBuilder<T> és a váró típusához Awaiter<T>van társítva:

using System.Runtime.CompilerServices; 
[AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
class MyTask<T>
{
    public Awaiter<T> GetAwaiter() { ... }
}

class Awaiter<T> : INotifyCompletion
{
    public void OnCompleted(Action completion) { ... }
    public bool IsCompleted { get; }
    public T GetResult() { ... }
}

záró példa

A feladatszerkesztő típusa egy adott tevékenységtípusnak megfelelő osztály vagy struktúratípus (15.14.2. §). A feladatszerkesztő típusának pontosan meg kell egyeznie a megfelelő tevékenységtípus deklarált akadálymentességével.

Megjegyzés: Ha a tevékenységtípus deklarálva internalvan, a megfelelő szerkesztőtípust internal is deklarálni kell, és ugyanabban a szerelvényben kell meghatározni. Ha a tevékenységtípus egy másik típusba van beágyazva, a tevékenység előlapjának típusát is ugyanabba a típusba kell ágyazni. végjegyzet

Az aszinkron függvények képesek felfüggeszteni a kiértékeléseket a testében lévő várakozási kifejezések (12.9.8. §) segítségével. A kiértékelést később a várakozási kifejezés felfüggesztésekor lehet folytatni egy újrakezdési meghatalmazott segítségével. Az újrakezdési delegált típusa típus System.Action, és a meghíváskor az aszinkron függvény meghívásának kiértékelése folytatódik a várakozási kifejezésből, ahol abbahagyta. Az aszinkron függvény hívásának aktuális hívója az eredeti hívó, ha a függvényhívást soha nem függesztették fel, vagy ellenkező esetben az újrakezdési delegált legutóbbi hívója.

15.14.2 Feladattípus-szerkesztő minta

A feladatszerkesztő-típus legfeljebb egy típusparaméterrel rendelkezhet, és nem ágyazható be általános típusba. A tevékenységszerkesztő-típusnak a következő tagokkal kell rendelkeznie (nem általános feladatszerkesztő-típusok SetResult esetén nincsenek paraméterek) deklarált public akadálymentességgel:

class «TaskBuilderType»<T>
{
    public static «TaskBuilderType»<T> Create();
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
                where TStateMachine : IAsyncStateMachine;
    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public «TaskType»<T> Task { get; }
}

A fordítónak olyan kódot kell létrehoznia, amely a "TaskBuilderType" használatával valósítja meg az aszinkron függvény kiértékelésének felfüggesztésének és újraértékelésének szemantikáját. A fordító a "TaskBuilderType" típust használja az alábbiak szerint:

  • «TaskBuilderType».Create() a rendszer meghívja, hogy létrehozza a listában elnevezett builder "TaskBuilderType" példányt.
  • builder.Start(ref stateMachine)a rendszer meghívja, hogy a szerkesztőt egy fordító által létrehozott állapotgép-példányhoz társítsa. stateMachine
    • Az építtetőnek be kell hívnia vagy stateMachine.MoveNext() vissza Start() kell hívnia Start() az állapotgépet.
  • A visszatérés után Start() a async metódus meghívja builder.Task a feladatot az aszinkron metódusból való visszatérésre.
  • Minden egyes hívás az stateMachine.MoveNext() állapotgépet fogja előléptetni.
  • Ha az állapotgép sikeresen befejeződött, builder.SetResult() meghívja a metódus visszatérési értékével, ha van ilyen.
  • Ellenkező esetben, ha a rendszer kivételt e ad az állapotgépbe, builder.SetException(e) a rendszer meghívja.
  • Ha az állapotgép eléri a await expr kifejezést, expr.GetAwaiter() a rendszer meghívja.
  • Ha a váró megvalósítja ICriticalNotifyCompletion a elemet, és IsCompleted hamis, az állapotgép meghívja builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).
    • AwaitUnsafeOnCompleted()a várakozó befejeződésekor a hívásokat a következővel awaiter.UnsafeOnCompleted(action) kell meghívnia.ActionstateMachine.MoveNext()
  • Ellenkező esetben az állapotgép meghívja a parancsot builder.AwaitOnCompleted(ref awaiter, ref stateMachine).
    • AwaitOnCompleted()a várakozó befejeződésekor a hívásokat a következővel awaiter.OnCompleted(action) kell meghívnia.ActionstateMachine.MoveNext()
  • SetStateMachine(IAsyncStateMachine) a fordító által létrehozott IAsyncStateMachine implementáció meghívhatja az állapotgép-példányhoz társított szerkesztőpéldány azonosítására, különösen olyan esetekben, amikor az állapotgép értéktípusként van implementálva.
    • Ha a szerkesztő hívstateMachine.SetStateMachine(stateMachine), a stateMachine program meghívja builder.SetStateMachine(stateMachine) a hozzá társított szerkesztőpéldánytstateMachine.

Megjegyzés: A paraméternek és SetResult(T result)az argumentumnak egyaránt «TaskType»<T> Task { get; } konvertálhatónak Tkell lennie. Ez lehetővé teszi, hogy a feladattípus-szerkesztő olyan típusokat támogatjon, mint például a csuplok, ahol két, nem azonos típus az identitáskonverter. végjegyzet

15.14.3 Feladatot visszaadó aszinkron függvény kiértékelése

A feladatvisszaküldött aszinkron függvény meghívásával létrejön a visszaadott feladattípus egy példánya. Ezt az aszinkron függvény visszatérési feladatának nevezzük. A tevékenység kezdetben hiányos állapotban van.

Az aszinkron függvény törzse ezután kiértékelésre kerül, amíg fel nem függeszti (egy váró kifejezés elérésével), vagy leáll, ahol a rendszer visszaadja a vezérlőt a hívónak a visszatérési feladattal együtt.

Amikor az aszinkron függvény törzse leáll, a visszaadott feladat ki lesz helyezve a hiányos állapotból:

  • Ha a függvény törzse egy visszatérési utasítás vagy a törzs vége elérése miatt leáll, a rendszer a visszatérési feladatban rögzíti az eredményértéket, amely sikeres állapotba kerül.
  • Ha a függvény törzse OperationCanceledExceptionmegszakítás nélkül leáll, a kivétel a megszakított állapotba helyezett visszatérési tevékenységben lesz rögzítve .
  • Ha a függvény törzse bármely más fel nem merült kivétel (13.10.6. §) eredményeként leáll, a kivételt a rendszer rögzíti a hibás állapotba helyezett visszatérési feladatban.

15.14.4 Érvénytelenítő aszinkron függvény kiértékelése

Ha az aszinkron függvény visszatérési típusa azvoid, a kiértékelés a fentiektől a következő módon tér el: Mivel a rendszer nem ad vissza feladatot, a függvény ehelyett a befejezést és a kivételeket közli az aktuális szál szinkronizálási környezetével. A szinkronizálási környezet pontos definíciója implementációfüggő, de az aktuális szál "hol" fut. A szinkronizálási környezet értesítést kap, ha egy void-returning async függvény kiértékelése megkezdődik, sikeresen befejeződik, vagy váratlan kivételt okoz.

Így a környezet nyomon követheti, hogy hány voidaszinkron függvény fut alatta, és eldöntheti, hogyan propagálja a kivételeket.

15.15 Szinkron és aszinkron iterátorok

15.15.1 Általános

A függvénytagot (12.6. §) vagy helyi függvényt (13.6.4. §) iterátorblokk használatával implementáltak (13.3. §) iterátornak nevezzük. A függvénytag törzseként iterátorblokk használható mindaddig, amíg a megfelelő függvénytag visszatérési típusa az enumerátori illesztők egyike (15.15.2. §) vagy a számba vehető illesztők egyike (15.15.3. §).

Az iterátorblokk (13.3. §) használatával implementált aszinkron függvényt (15.14.§) aszinkron iterátornak nevezzük. A függvénytag törzseként aszinkron iterátorblokk használható mindaddig, amíg a megfelelő függvénytag visszatérési típusa az aszinkron enumerátor-illesztők (15.15.2. §) vagy az aszinkron számba vehető illesztők (15.15.3. §).

Az iterátorblokkok előfordulhatnak method_body, operator_body vagy accessor_body formájában, míg az események, a példánykonstruktorok, a statikus konstruktorok és a véglegesítők nem implementálhatók szinkron vagy aszinkron iterátorként.

Ha egy függvénytagot vagy helyi függvényt iterátorblokk használatával implementál, fordítási időhibát okoz, ha a függvénytag paraméterlistájában bármely in, out vagy ref típusú paramétert vagy ref struct típusú paramétert határoz meg.

15.15.2 Felsoroló felületek

Az enumerátor interfészek a nem általános felület System.Collections.IEnumerator és az általános felületek minden példánya System.Collections.Generic.IEnumerator<T>.

Az aszinkron enumerátor interfészek az általános felület System.Collections.Generic.IAsyncEnumerator<T>összes példánya.

A rövidség kedvéért ebben az alklamzatban és testvéreiben, ezekre az interfészekre IEnumerator-ként, IEnumerator<T>-ként és IAsyncEnumerator<T>-ként hivatkozunk.

15.15.3 Felsorolható felületek

A felsorolható interfészek a nem általános interfész System.Collections.IEnumerable és az általános interfészek minden példánya System.Collections.Generic.IEnumerable<T>.

Az aszinkron számba vehető felületek mind az általános felület System.Collections.Generic.IAsyncEnumerable<T>példányai.

A rövidség kedvéért ebben az alklamzatban és testvéreiben, ezekre az interfészekre IEnumerable-ként, IEnumerable<T>-ként és IAsyncEnumerable<T>-ként hivatkozunk.

15.15.4 Hozamtípus

Az iterátor azonos típusú értékek sorozatát hozza létre. Ezt a típust az iterátor hozamtípusának nevezzük.

  • Az iterátor hozamtípusa, amely visszaadja IEnumerator vagy IEnumerable az object.
  • Az iterátor hozamtípusa, amely egy IEnumerator<T>, IAsyncEnumerator<T>, IEnumerable<T>, vagy IAsyncEnumerable<T> értéket ad vissza, T.

15.15.5 Enumnak objektumok

15.15.5.1 Általános

Ha egy enumerátor felülettípust visszaadó függvénytag vagy helyi függvény iterátorblokk használatával van implementálva, a függvény meghívása nem hajtja végre azonnal a kódot az iterátorblokkban. Ehelyett létrejön és visszaad egy enumerátorobjektumot. Ez az objektum beágyazza az iterátorblokkban megadott kódot, és az iterátorblokkban a kód végrehajtására akkor kerül sor, amikor az enumerátor objektum MoveNext vagy MoveNextAsync metódusát meghívják. Az enumerátorobjektumok jellemzői a következők:

  • Megvalósítja a System.IDisposable, IEnumerator és IEnumerator<T> vagy System.IAsyncDisposable és IAsyncEnumerator<T>, ahol T az iterátor hozamtípusa.
  • Inicializálva van az argumentumértékek egy példányával (ha van ilyen), és a függvénytagnak átadott példányérték.
  • Négy lehetséges állapotban van, előtte, fut, felfüggesztve és utána, és kezdetben az előző állapotban van.

Az enumerátorobjektumok általában egy fordító által generált enumerátorosztály példányai, amelyek az iterátorblokkban beágyazják a kódot, és implementálják az enumerátor felületeit, de más implementálási módszerek is lehetségesek. Ha a fordító enumerátorosztályt hoz létre, az osztály közvetlenül vagy közvetve beágyazva lesz a függvénytagot tartalmazó osztályba, privát akadálymentességgel fog rendelkezni, és a fordító számára fenntartott névvel fog rendelkezni (6.4.3. §).

Az enumerátorobjektumok a fent megadottaknál több illesztőt implementálhatnak.

Az alábbi alklámok azt írják le, hogy a tagnak milyen viselkedésre van szüksége az enumerátor előléptetéséhez, az aktuális érték lekéréséhez az enumerátorból, és az enumerátor által használt erőforrások felszabadításához. Ezeket a szinkron és aszinkron enumerátorokhoz a következő tagok határozzák meg:

  • Az enumerátor előléptetéséhez: MoveNext és MoveNextAsync.
  • Az aktuális érték lekérése: Current.
  • Az erőforrások kezeléséhez: Dispose és DisposeAsync.

Az enumerátorobjektumok nem támogatják a metódust IEnumerator.Reset . Ennek a metódusnak a meghívása dobást okoz System.NotSupportedException .

A szinkron és az aszinkron iterátorblokkok abban különböznek, hogy az aszinkron iterátortagok feladattípusokat adnak vissza, és várhatók.

15.15.5.2 Az enumerátor továbblépése

Az MoveNext enumerátorobjektumok és MoveNextAsync metódusok egy iterátorblokk kódját foglalja magában. Az or MoveNext metódus meghívása végrehajtja a MoveNextAsync kódot az iterátorblokkban, és szükség szerint beállítja az Current enumerátor objektum tulajdonságát.

MoveNext olyan bool értéket ad vissza, amelynek jelentését az alábbiakban ismertetjük. MoveNextAsync egy értéket ad ValueTask<bool> vissza (15.14.3. §). A MoveNextAsync eredményértéke ugyanazt a jelentést hordozza, mint a MoveNext eredményértéke. A következő leírásban a MoveNext leírt műveletek a MoveNextAsync esetében az alábbi eltéréssel alkalmazhatók: Amikor azt állítjuk, hogy a MoveNext visszaadja a true vagy false értéket, a MoveNextAsync beállítja a tevékenység állapotát befejezett-re, és a tevékenység eredményértékét a megfelelő true vagy false értékre állítja.

Az MoveNext vagy MoveNextAsync által végrehajtott pontos művelet attól függ, hogy a meghíváskor milyen állapotban van az enumerátor objektum.

  • Ha az enumerátor objektum állapota korábban van, invokálásMoveNext:
    • Módosítja a futó állapotot.
    • Inicializálja az iterátorblokk paramétereit (beleértve this) az argumentumértékekhez és a példányértékhez, amelyet az enumerátor-objektum inicializálásakor mentettek.
    • Az iterátorblokkot az elejétől a végrehajtás megszakításáig hajtja végre (az alábbiak szerint).
  • Ha az enumerátor objektum állapota fut, az invokálás MoveNext eredménye meghatározatlan.
  • Ha az enumerátor objektum állapota fel van függesztve, a MoveNext parancs meghívása:
    • Módosítja a futó állapotot.
    • Visszaállítja az összes helyi változó és paraméter értékét (beleértve ) thisaz iterátorblokk utolsó felfüggesztésekor mentett értékekre.

      Megjegyzés: Az ilyen változók által hivatkozott objektumok tartalma az előző hívás MoveNextóta változhatott. végjegyzet

    • Azonnal folytatja az iterátorblokk végrehajtását a végrehajtás felfüggesztését okozó hozamvisszautasítást követően, és a végrehajtás megszakításáig folytatódik (az alábbiak szerint).
  • Ha az enumerátor objektum állapota utána van, a meghívás MoveNext hamis értéket ad vissza.

Az iterátorblokk végrehajtásakor MoveNext a végrehajtás négy módon szakítható meg: yield return utasítással, utasítással yield break , az iterátorblokk végének találkozásával, valamint az iterátorblokkból kidobott és propagált kivétellel.

  • yield return Utasítás észlelésekor (9.4.4.20. §):
    • Az utasításban megadott kifejezés kiértékelése, implicit módon a hozamtípussá alakítása és az Current enumerátor objektum tulajdonságához van rendelve.
    • Az iterátor törzsének végrehajtása fel van függesztve. A rendszer menti az összes helyi változó és paraméter (beleértve this) értékeit, ahogyan az utasítás helye yield return is. Ha az yield return utasítás egy vagy több try blokkon belül van, akkor a társított végső blokkok végrehajtása jelenleg nem történik meg.
    • Az enumerátor objektum állapota fel van függesztve.
    • A MoveNext metódus visszatér true a hívóhoz, jelezve, hogy az iteráció sikeresen haladt a következő értékre.
  • yield break Utasítás észlelésekor (9.4.4.20. §):
    • Ha az yield break utasítás egy vagy több try blokkon belül van, a rendszer végrehajtja a társított finally blokkokat.
    • Az enumerátor objektum állapota a következőre módosul.
    • A MoveNext metódus visszatér false a hívóhoz, jelezve, hogy az iteráció befejeződött.
  • Amikor az iterátor törzsének vége megjelenik:
    • Az enumerátor objektum állapota a következőre módosul.
    • A MoveNext metódus visszatér false a hívóhoz, jelezve, hogy az iteráció befejeződött.
  • Ha kivételt dobnak ki és propagálnak az iterátorblokkból:
    • Az iterátor törzsének megfelelő finally blokkokat a kivételpropagálás hajtja végre.
    • Az enumerátor objektum állapota a következőre módosul.
    • A kivételpropagálás továbbra is a metódus hívójára MoveNext terjed ki.

15.15.5.3 Az aktuális érték lekérése

Az enumerátorobjektumok tulajdonságát Current az iterátorblokkban lévő utasítások befolyásolják yield return .

Megjegyzés: A Current tulajdonság szinkron és aszinkron iterátorobjektumok szinkron tulajdonsága. végjegyzet

Ha egy enumerátor objektum felfüggesztett állapotban van, az érték Current az előző hívás MoveNextáltal beállított érték. Ha egy enumerátor-objektum az előző, a futó vagy a követő állapotban van, a hozzáférés Current eredménye meghatározatlan.

Nem hozamtípusú objectiterátor esetén az enumerátor objektum implementációján CurrentIEnumerable keresztüli hozzáférés eredménye az enumerátor objektum implementációján CurrentIEnumerator<T> keresztüli hozzáférésnek felel meg, és az eredményt a következőre objectadhatja.

15.15.5.4 Erőforrások eltávolítása

A Dispose vagy DisposeAsync metódus az iteráció törlésére szolgál az enumerátor objektum utóállapotba állításával.

  • Ha az enumerátor objektum állapota korábban van, a meghívás utánra módosítja az állapototDispose.
  • Ha az enumerátor objektum állapota fut, az invokálás Dispose eredménye meghatározatlan.
  • Ha az enumerátor objektum állapota fel van függesztve, invokálás Dispose:
    • Módosítja a futó állapotot.
    • Az utolsó blokkokat úgy hajtja végre, mintha az utolsó végrehajtott yield return utasítás egy yield break utasítás volna. Ha ez kivételt okoz az iterátor törzséből, az enumerátor objektum állapota utána lesz beállítva, és a kivétel propagálása a Dispose metódus hívójára történik.
    • A következőre módosítja az állapotot.
  • Ha az enumerátor objektum állapota utána van, a meghívásnak Dispose nincs hatása.

15.15.6 Felsorolható objektumok

15.15.6.1 Általános

Ha egy számozható illesztőtípust visszaadó függvénytag vagy helyi függvény iterátorblokk használatával van implementálva, a függvénytag meghívása nem hajtja végre azonnal a kódot az iterátorblokkban. Ehelyett egy számbavételre alkalmas objektum jön létre és ad vissza.

Az enumerálható objektum GetEnumerator vagy GetAsyncEnumerator metódusa egy olyan enumerátorobjektumot ad vissza, amely az iterátorblokkban megadott kódot foglalja magában; az iterátorblokkban lévő kód végrehajtása az enumerátorobjektum MoveNext vagy MoveNextAsync metódusának meghívásakor történik. Az enumerálásra alkalmas objektumok jellemzői a következők:

  • Implementálja IEnumerable és IEnumerable<T> vagy IAsyncEnumerable<T>, ahol T az iterátor hozamtípusa.
  • Inicializálva van az argumentumértékek egy példányával (ha van ilyen), és a függvénytagnak átadott példányérték.

Az enumerálható objektumok általában egy fordító által létrehozott számbavételi osztály példányai, amelyek beágyazják a kódot az iterátorblokkba, és implementálják az enumerálható interfészeket, de más implementálási módszerek is lehetségesek. Ha egy számba vehető osztályt a fordító hoz létre, az osztály közvetlenül vagy közvetve beágyazva lesz a függvénytagot tartalmazó osztályba, privát akadálymentességgel fog rendelkezni, és a fordító számára fenntartott névvel fog rendelkezni (6.4.3. §).

Az enumerálható objektumok több felületet implementálhatnak, mint a fent megadottak.

Megjegyzés: Előfordulhat például, hogy egy számozható objektum is implementálható IEnumerator , és IEnumerator<T>lehetővé teszi, hogy számbavételi és számbavételi értékként is szolgáljon. Egy ilyen implementáció általában a saját példányát adja vissza (a foglalások mentéséhez) az első hívásból GetEnumerator. A későbbi meghívások – ha vannak ilyenek GetEnumerator– egy új osztálypéldányt adnának vissza, amely általában ugyanahhoz az osztályhoz tartozik, így a különböző enumerátorpéldányokra irányuló hívások nem lesznek hatással egymásra. Nem tudja visszaadni ugyanazt a példányt akkor sem, ha az előző enumerátor már szerepel a sorozat végén, mivel a kimerült számba vevő összes jövőbeli hívásának kivételeket kell tartalmaznia. végjegyzet

15.15.6.2 A GetEnumerator vagy a GetAsyncEnumerator metódus

A számbavételi objektumok a metódusok és GetEnumerator interfészek implementálását IEnumerableIEnumerable<T> biztosítják. A két GetEnumerator metódus közös implementációval rendelkezik, amely egy elérhető enumerátorobjektumot szerez be és ad vissza. Az enumerátor objektum inicializálva van az argumentumértékekkel és a példányértékével, amelyet a számozható objektum inicializálásakor mentettek, máskülönben az enumerátor objektum úgy működik, ahogyan a 15.15.5-ben le van írva.

Az aszinkron számbavételi objektum biztosítja a GetAsyncEnumerator metódus implementálását a IAsyncEnumerable<T> interfészben. Ez a metódus egy elérhető aszinkron enumerátorobjektumot ad vissza. Az enumerátor objektum inicializálva van az argumentumértékekkel és a példányértékével, amelyet a számozható objektum inicializálásakor mentettek, máskülönben az enumerátor objektum úgy működik, ahogyan a 15.15.5-ben le van írva.