Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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 internal
private
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űennull
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ódustF
vezet be. Az osztályB
egy további módszertG
vezet be, de mivel nem nyújt implementációt,F
absztraktnak is kell minősíteniB
. OsztályC
felülbírálásokF
, és tényleges megvalósítást biztosít. Mivel nincsenek absztrakt tagok aC
,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ó vagyabstract
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 protected
vagyprotected 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 namespace_or_type_name az
T
, vagy - A namespace_or_type-név az
T
űrlap typeof_expression (typeof(T)
.§) része.
A primary_expression (12.8. §) akkor hivatkozhat statikus osztályra, ha
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ályaB
, ésB
azt mondják , hogy abból származikA
. MivelA
explicit módon nem határoz meg közvetlen alaposztályt, a közvetlen alaposztály implicit módonobject
van 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ő lenneB<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.Enum
vagy 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 A
közvetlen alaposztály-specifikációjának jelentésének meghatározásakor a rendszer ideiglenesen feltételeziB
, hogy az alaposztály-specifikáció B
object
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álytZ
tekintikobject
annak , ezért (a 7.8. § szabályai szerint)Z
nem tekinthető tagnakY
.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[]>
ésB<IComparable<int[]>>
A
object
.záró példa
Az osztály object
kivé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üggB
(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 (mivelA
mind a közvetlen alaposztálya, mind a közvetlenül magában foglaló osztálya), deA
nem függB
(mivelB
nem alaposztály, és nem is tartalmazza azA
osztá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ólA
pró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 azIA
,IB
ésIC
.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
vagyT : BaseClass
), hanem használjaT?
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étT
adja meg. Így a formákT??
rekurzívan felépített típusai ésNullable<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
vagySystem.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éterS
kényszereként, attólS
függ.T
- Ha egy típusparaméter
S
egy típusparamétertőlT
függ, ésT
egy típusparamétertőlU
függ, akkorS
attó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
ígyS
arra kényszerítik, hogy ugyanaz legyen, mintT
a típus, így nincs szükség két típusparaméterre. - Ha
S
rendelkezik az értéktípusra vonatkozó korlátozással, akkorT
nem lehet class_type kényszer. - Ha
S
class_type kényszerrel rendelkezikA
- Ha
S
a típusparamétertőlU
is függ, ésU
class_type, ésA
class_typeT
, akkor identitáskonvertálást vagy implicit referenciaátalakítást kell létrehozni a típusról a másikraB
A
tö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.ValueType
System.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ípusOuter.Inner
, akkorCₓ
beágyazott típusOuterₓ.Innerₓ
. - Ha
C
Cₓ
egy típusargumentumokkalG<A¹, ..., Aⁿ>
rendelkező, konstruktált típusA¹, ..., Aⁿ
, akkorCₓ
a létrehozott típusG<A¹ₓ, ..., Aⁿₓ>
. - Ha
C
tömbtípusE[]
, akkorCₓ
a tömb típusaEₓ[]
. - Ha
C
dinamikus, akkorCₓ
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éhezR
tartalmazza a tényleges alaposztályt. - A struktúratípus
T
minden egyes kényszereR
tartalmazza a következőtSystem.ValueType
: . - Az enumerálási típus
T
minden egyes kényszereR
tartalmazza a következőtSystem.Enum
: . - A delegált típusú
T
összes korlátozásR
tartalmazza a dinamikus törlést. - A tömbtípus
T
minden egyes kényszereR
tartalmazza a következőtSystem.Array
: . - Az osztálytípus
T
minden egyes kényszereR
tartalmazza a dinamikus törlést.
Akkor
- Ha
T
értéktípus-korlátozással rendelkezik, akkor a tényleges alaposztálya aSystem.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 azT
.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_type
T
halmaza é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 object
nemSystem.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ülx
hívhatók meg, mertT
az mindig implementálásraIPrintable
van 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 class
elő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
in
az ,out
ésref
.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
ésout
.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ő tagT
típusa "egydimenziós tömb kétdimenziós tömbjea
" vagyint
.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ármazikB
, ésB
származikA
, akkorC
ö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 tagotint
G(string s)
kap a típusparaméterint
típusargumentumánakT
helyettesítésével.D<int>
az osztály deklarációjánakB
ö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átB<int[]>
D<int>
az alaposztály specifikációjánakint
helyettesítésévelT
B<T[]>
határozzuk meg . Ezt követően a típusargumentumB
int[]
helyett a függvény azU
public U F(long index)
öröklött tagotpublic 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 protected
internal
vagy 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.M
E
Ez egy példány jelölésére vonatkozó fordításiE
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
M
az űrlap egy member_access hivatkozik (E.M
. §), egy olyan példányt jelöl ,E
amelynek tagjaM
van . 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. AG
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. AMain
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ályonA
belül deklarálva van, és az osztályA
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ég
public
(vagyinternal
) három formájának bármelyikével rendelkezhet,private
és a többi strukturált taghozprivate
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
Node
deklará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étrehoznakNested
egy példányt, és átadják aNested
saját példányát a konstruktornak, hogy későbbi hozzáférést biztosítsanak a példánytagok számáraC
.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 private
protected
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
C
Nested
jelenít meg. Ezen belülNested
a metódusG
meghívja a megadottF
statikus metódustC
, ésF
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álybanF
definiált védett metódushozDerived
fér hozzá egyBase
példányDerived
meghí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
Inner
való 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:
- 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.
- 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.
- 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ágotP
határoz meg, így megőrizve az aláírásokat ésget_P
aset_P
metódusokat.A
osztályB
mindkét fenntartott aláírásból származikA
, é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ú E
esemé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
, uint
long
ulong
char
float
double
decimal
bool
string
enum_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 case
enum
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
string
létrehozásának egyetlen módja az operátor alkalmazásanew
, és mivel aznew
operátor nem engedélyezett egy constant_expression, a reference_typestring
egyetlen 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éseireadonly
(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, majdB.Z
-et, és végülA.X
-t, ezáltal előállítva a10
,11
és12
é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
A
B
külön programokban deklarálták őket, akkor lehetA.X
függeniB.Z
, deB.Z
nem lehet egyszerre függeniA.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
,Red
Green
ésBlue
a tagok nem deklarálhatók const tagokként, mert értékük fordításkor nem számítható ki. A deklarálásnakstatic 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étProgram2
külön lefordított programot jelölnek. MivelProgram1.Utils.X
mezőkéntstatic readonly
van deklarálva, azConsole.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ékX
módosul, ésProgram1
újrafordított, azConsole.WriteLine
utasítás akkor is kiadja az új értéket, haProgram2
nem fordítja újra. Ha azonban állandó lett volnaX
, akkor az értékX
a fordításkorProgram2
lett volna beállítva, és az újrafordításigProgram1
változatlanProgram2
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
,int
uint
,char
,float
,bool
,System.IntPtr
, vagySystem.UIntPtr
. - Olyan enum_type , amely enum_base típusú
byte
,sbyte
,short
,ushort
,int
vagyuint
.
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ódustThread2
futtatja. Ez a metódus egy értéket egy nem illékony, úgynevezettresult
mezőbe tárol, majd az illékony mezőbentrue
tároljafinished
. A főszál megvárja, amíg a mezőfinished
be van állítvatrue
, majd beolvassa a mezőtresult
. A deklarálásfinished
ótavolatile
a fő szálnak be kell olvasnia a mező143
értékétresult
. Ha a mezőtfinished
nem deklaráltákvolatile
, akkor megengedett, hogyresult
az áruház látható legyen a főszálon , és így a főszál beolvassa a mezőbőlfinished
a 0 értéket . A mezőkéntfinished
való deklarálásvolatile
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
merti
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 feladatokati
, éss
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
ésb
, a program érvényes. Ez a kimenetet eredményezia = 1, b = 2
mert a statikus mezők
a
b
inicializálása0
(az alapértelmezett értékint
) az inicializálók végrehajtása előtt történik. A futtatásoka
inicializálójánakb
értéke nulla, és ígya
inicializálva lesz.1
Amikor a futtatásokb
inicializálója, az egy értéke már1
meg van adva, és ígyb
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
X
az inicializáló végrehajtásaY
bá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
B
a statikus konstruktornak (és így aB
statikus mező inicializálóinak) a statikus konstruktor és a mező inicializálói előttA
kell 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
ésoverride
. - A deklaráció legfeljebb az alábbi módosítók egyikét tartalmazza:
new
ésoverride
. - 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
,sealed
vagyextern
. - 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
,override
vagyabstract
. - Ha a deklaráció tartalmazza a
sealed
módosítót, akkor a deklaráció tartalmazza aoverride
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
,protected
internal
,private
,virtual
,sealed
, ,override
, ,abstract
, vagyextern
.
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,
void
akkor 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 in
az , 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
, ref
vagy 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 out
nem 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, amelyS
értéktípus - annak az űrlapnak
default(S)
a kifejezése, amelyS
é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_list
M
i
egy kötelezőref
paraméter,d
egy kötelező értékparaméter,b
s
o
ést
választható értékparaméterek, ésa
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:
- Értékparaméterek (§15.6.2.2).
- Bemeneti paraméterek (§15.6.2.3.2).
- Kimeneti paraméterek (§15.6.2.3.4).
- Referenciaparaméterek (§15.6.2.3.3.3).
- Paramétertömbök (§15.6.2.4).
Megjegyzés: A 7.6. §-ban leírtak szerint a
in
,out
, ésref
módosítók a metódus aláírásának részei, de aparams
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 in
ref
out
azonos 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
Swap
Main
x
a következőt jelölii
ésy
jelöli.j
Így a meghívásnak az a hatása, hogy felcseréli az ési
j
a .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
, minds
a
pedig a .b
Ezért a meghíváshoz a neveks
a
b
é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ókname
hozzárendelése nem rendelhető hozzá, mielőtt átadjákSplitPath
ő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ípusstring[,]
nem. záró példa
Megjegyzés: A módosító nem kombinálható
params
a módosítókkalin
,out
vagyref
. 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ötarr
értékparaméterként. Az F második meghívása automatikusan létrehoz egy négy elemetint[]
a megadott elemértékekkel, és értékparaméterként átadja a tömbpéldányt. Hasonlóképpen, a harmadik meghívásF
nulla elemetint[]
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 felelF(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
F
a normál formátumF
azért alkalmazható, mert implicit átalakítás létezik az argumentumtípustól a paramétertípusig (mindkettő típusobject[]
). Így a túlterhelés feloldása kiválasztja a normál formájátF
, é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átumF
nem alkalmazható, mert az argumentumtípustól a paramétertípusig nem létezik implicit átalakítás (a típusobject
nem konvertálható implicit módon típussáobject[]
). A kiterjesztett formátumF
azonban alkalmazható, ezért a túlterhelés feloldása választja ki. Ennek eredményeképpen egy egy elemetobject[]
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 egyreobject[]
).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
C
alkalmazza az ,N
ésA
az adott metódusM
kiválasztásához a deklarált és öröklőC
metó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ívjaM
R
a legelvezetettebb implementációt.
- Ha
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ótM
, akkor ez a legelvezetettebb implementáció aM
tekintetbenR
. - Ellenkező esetben, ha
R
a felülbírálástM
tartalmazza , akkor ez a legelvezetettebb implementáció aM
tekintetbenR
. - Ellenkező esetben a legelvezetettebb végrehajtás
M
a közvetlenR
alaposztályM
vonatkozá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ódustF
G
mutat be. Az osztály egy új, nem virtuális metódustB
vezet be , amely elrejti az örökölt metódustF
, é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.G
A.G
Ennek az az oka, hogy a példány futásidejű típusa (vagyisB
) nem a példány fordítási idejének típusa (vagyisA
) 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
ésD
az osztályok két virtuális metódust tartalmaznak ugyanazzal az aláírással: Az, amelyik általA
bevezetett és az általukC
bevezetett. A bevezetettC
metódus elrejti az örökölt metódustA
. Így a felülbírálásiD
deklaráció felülbírálja a metódus általC
bevezetett metódust , és nem lehetD
felülbírálni az által bevezetett metódustA
. 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 M
C
é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 public
az , ha protected
van , vagy protected internal
ha az , vagy ugyanabban a programban deklarálva van internal
private 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 inB
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ásB
meg lett írva((A)this).PrintFields()
, az rekurzív módon hívja meg aPrintFields
deklaráltB
metódust , nem pedig a deklarált metódustA
, mivelPrintFields
virtuális, és a futási idő típusa((A)this)
azB
.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 nemB
tartalmaz módosítótoverride
, ezért nem bírálja felül a metódust aF
következőbenA
: . Ehelyett aF
metódusB
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 elrejtiB
a virtuálisF
metódust, amelytőlA
öröklődik. Mivel az újF
verzióB
privát hozzáféréssel rendelkezik, hatóköre csak az osztály törzsétB
tartalmazza, és nem terjed ki a következőreC
: . Ezért az in deklarációjaF
felülbírálhatja azC
ö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ódustF
, amely rendelkezik asealed
módosítóval, és egy olyan metódustG
, amely nem.B
A módosító használata megakadályozzasealed
aC
további felülsúlyozástF
.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. APaint
metódus absztrakt, mert nincs értelmes alapértelmezett implementáció. AzEllipse
osztályok konkrétBox
Shape
megvalósítások. Mivel ezek az osztályok nem absztraktak, felül kell bírálniuk a metódustPaint
, é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ályB
absztrakciós módszerrel felülbírálja ezt a metódust, az osztályC
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
extern
DllImport
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 private
tö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ő astring[]
, és aToInt32
metódus elérhető,string
mert 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 void
az, 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. AG
ésH
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. AI
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
argumentumotout
átadni,ref
kivé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 set
egy 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_modifier
readonly
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éggel
public
, akkor a accessor_modifier által deklarált akadálymentesség lehetprivate protected
,protected internal
vagyinternal
protected
private
. - Ha a tulajdonság vagy az indexelő rendelkezik deklarált akadálymentességgel
protected internal
, akkor a accessor_modifier által deklarált akadálymentesség lehetprivate protected
,protected private
vagyinternal
protected
private
. - Ha a tulajdonság vagy indexelő rendelkezik deklarált akadálymentességgel
internal
vagyprotected
, akkor az accessor_modifierdeklarált akadálymentesség vagyprivate protected
. - Ha az ingatlan vagy indexelő rendelkezik deklarált akadálymentességgel
private 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 .
- Ha a tulajdonság vagy az indexelő rendelkezik deklarált akadálymentességgel
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_bodyabstract
vonatkozó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 void
15.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, value
ez 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öztulajdontCaption
deklarál. A Caption tulajdonság lekéréses tartozéka astring
privátcaption
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 adprivate
vissza, és a készlet tartozéka módosítja eztprivate
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ágB
elrejti a tulajdonságotP
A
mind az olvasás, mind az írás szempontjából. Így az utasításokbanB 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 csakP
olvasható tulajdonság elrejti a fájlbanB
lévő írásvédettP
tulajdonságotA
. Vegye figyelembe azonban, hogy a rejtettP
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étint
mezőt használ,x
ésy
a helyét tárolja. A hely nyilvánosan elérhető mind tulajdonságkéntX
Y
, mind típustulajdonságkéntLocation
Point
. Ha egy későbbi verzióbanLabel
kényelmesebbé válik a helyPoint
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
Hay
mezők lettekpublic readonly
volna, akkor lehetetlen lett volna ilyen módosítást végezni azLabel
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,In
Out
amelyekError
a szabványos bemenetet, kimenetet és hibaeszközöket jelölik. Ha tulajdonságokként teszi ki ezeket a tagokat, az osztály késleltetheti azConsole
inicializálást, amíg azok ténylegesen használatba nem kerülnek. Például, amikor először hivatkozik aOut
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 aIn
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.Text
B.Text
, még olyan környezetekben is, ahol csak a beállított tartozékot hívja meg. Ezzel szemben a tulajdonságB.Count
nem érhető el az osztályM
számára, ezért a rendszer inkább az akadálymentes tulajdonságotA.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 private
accessor_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 abstract
override
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, ésZ
egy absztrakt írási-olvasási tulajdonság. MivelZ
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ók
X
Y
, ésZ
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ékX
és a készlet tartozékaY
az alap kulcsszót használja az örökölt tartozékok eléréséhez. A felülbírálások deklarálásaZ
mindkét absztrakt tartozékot felülírja – így nincsenek kiemelkedőabstract
függvénytagokB
a alkalmazásban, ésB
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_initializerabstract
vagy 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 x
a 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étButton
példányt hoz létre, és eseménykezelőket csatol azClick
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üliButton
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. AzOnClick
osztály metódusaButton
"emeli" az eseménytClick
. 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 aClick
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, ésClick –= 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
X
belül a bal oldalon lévő hivatkozásokEv
é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ásEv
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, value
fordí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. AAddEventHandler
metódus hozzárendel egy delegált értéket egy kulccsal, aGetEventHandler
metódus visszaadja a kulcshoz jelenleg társított delegáltat, aRemoveEventHandler
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ó this
kö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ént
in
,out
ref
kivé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óbbibyte
helyett), de ugyanazt a műveletet teszi lehetővé, mint egybool[]
.Az alábbi
CountPrimes
osztály egyBitArray
é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 egybool[]
.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, elnevezettvalue
paramé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.P
használatával érhető el, aholP
a tulajdonság neve. Egy felülíró indexelő deklarációban az örökölt indexelő a szintaxissalbase[E]
érhető el, aholE
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 egystatic
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étertT
kell alkalmaznia, vagyT?
bármilyen típust visszaadhat. - A nem jegyzőnek
++
vagy--
operátornak egyetlen típusparamétertT
kell alkalmaznia, vagyT?
ugyanazt a típust vagy az abból származtatott típust kell visszaadni. - A nem jegyzőnek
true
vagyfalse
operátornak egyetlen típusparamétertT
kell alkalmaznia, vagyT?
a típustbool
kell visszaadni.
A nem kötelező operátorok aláírása az operátori jogkivonatból (+
, -
, !
, ~
, ++
, , --
, , vagy true
false
) é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
vagyT?
, é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ípusT
vagyT?
, a második típusnak pedigint
vagyint?
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 T
null 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ő S
T
és megfelelő. Egy osztály vagy szerkezet csak akkor deklarálhat egy forrástípusból céltípusra S
T
való átalakítást, ha az alábbiak mindegyike igaz:
S₀
ésT₀
különböző típusok.S₀
VagyT₀
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
S
T
vagy onnan:T
S
.
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
T
int
string
és , illetve egyedi, kapcsolat nélküli típusnak minősülnek. A harmadik operátor azonban hiba, mertC<T>
a függvény alaposztályaD<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ástC
int
int
C
, de nemint
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éntT
van 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ípusigT
létezik, a rendszer figyelmen kívül hagyja a felhasználó által definiált (implicit vagy explicit)S
T
konverziókat. - Ha egy előre definiált explicit átalakítás (10.3.§) típustól
S
típusigT
létezik, a felhasználó által definiált explicit konverziókatS
T
a rendszer figyelmen kívül hagyja. Továbbá:- Ha vagy
S
T
felülettípus, a felhasználó által definiált implicit konverziókS
T
figyelmen kívül lesznek hagyva. - Ellenkező esetben a felhasználó által definiált implicit konverziók
S
T
továbbra is figyelembe lesznek véve.
- Ha vagy
Az összes típus esetében, de object
a 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
object
azonban 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
Digit
byte
implicit, mert soha nem ad kivételt vagy nem vesz el információt, de az átállításbyte
Digit
explicit, mivelDigit
csak a lehetséges értékek egy részhalmazátbyte
jelö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 object
kivé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álnakB
, 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éky
azonban 0 (egy alapértelmezett értéke), mert a hozzárendelésint
csak az alaposztály-konstruktor visszatérésey
utá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éldaclass 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
ésthis
). 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
A
a hívásA.F
aktiválja, a statikus konstruktor végrehajtásátB
pedig a hívásB.F
indí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ótB.Y
futtatja az osztályB
statikus konstruktora előtt.Y
Az inicializáló azért futtatjaA
static
a konstruktort, mert az értékreA.X
hivatkozik. A statikus konstruktorA
ennek során kiszámítja az érték,X
és ezzel lekéri az alapértelmezett értéketY
, amely nulla.A.X
inicializálása így 1-re történik. A statikus mező inicializálóinak és statikus konstruktorainak futtatásánakA
folyamata ezután befejeződik, visszatérve a kezdeti értékY
kiszá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.Object
a metódustFinalize
.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 , in
vagy 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 T
eredmé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áhozMyTaskMethodBuilder<T>
és a váró típusáhozAwaiter<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
internal
van, a megfelelő szerkesztőtípustinternal
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 elnevezettbuilder
"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()
visszaStart()
kell hívniaStart()
az állapotgépet.
- Az építtetőnek be kell hívnia vagy
- A visszatérés után
Start()
aasync
metódus meghívjabuilder.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, ésIsCompleted
hamis, az állapotgép meghívjabuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitUnsafeOnCompleted()
a várakozó befejeződésekor a hívásokat a következővelawaiter.UnsafeOnCompleted(action)
kell meghívnia.Action
stateMachine.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ővelawaiter.OnCompleted(action)
kell meghívnia.Action
stateMachine.MoveNext()
-
-
SetStateMachine(IAsyncStateMachine)
a fordító által létrehozottIAsyncStateMachine
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ív
stateMachine.SetStateMachine(stateMachine)
, astateMachine
program meghívjabuilder.SetStateMachine(stateMachine)
a hozzá társított szerkesztőpéldánytstateMachine
.
- Ha a szerkesztő hív
Megjegyzés: A paraméternek és
SetResult(T result)
az argumentumnak egyaránt«TaskType»<T> Task { get; }
konvertálhatónakT
kell 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
OperationCanceledException
megszakí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 void
aszinkron 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
vagyIEnumerable
azobject
. - Az iterátor hozamtípusa, amely egy
IEnumerator<T>
,IAsyncEnumerator<T>
,IEnumerable<T>
, vagyIAsyncEnumerable<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
ésIEnumerator<T>
vagySystem.IAsyncDisposable
ésIAsyncEnumerator<T>
, aholT
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
ésMoveNextAsync
. - Az aktuális érték lekérése:
Current
. - Az erőforrások kezeléséhez:
Dispose
ésDisposeAsync
.
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ás
MoveNext
:- 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 )
this
az 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 helyeyield return
is. Ha azyield return
utasítás egy vagy többtry
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értrue
a hívóhoz, jelezve, hogy az iteráció sikeresen haladt a következő értékre.
- Az utasításban megadott kifejezés kiértékelése, implicit módon a hozamtípussá alakítása és az
-
yield break
Utasítás észlelésekor (9.4.4.20. §):- Ha az
yield break
utasítás egy vagy többtry
blokkon belül van, a rendszer végrehajtja a társítottfinally
blokkokat. - Az enumerátor objektum állapota a következőre módosul.
- A
MoveNext
metódus visszatérfalse
a hívóhoz, jelezve, hogy az iteráció befejeződött.
- Ha az
- 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érfalse
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.
- Az iterátor törzsének megfelelő
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ú object
iterátor esetén az enumerátor objektum implementációján Current
IEnumerable
keresztüli hozzáférés eredménye az enumerátor objektum implementációján Current
IEnumerator<T>
keresztüli hozzáférésnek felel meg, és az eredményt a következőre object
adhatja.
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 állapotot
Dispose
. - 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 egyyield 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 aDispose
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
ésIEnumerable<T>
vagyIAsyncEnumerable<T>
, aholT
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
, ésIEnumerator<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ólGetEnumerator
. A későbbi meghívások – ha vannak ilyenekGetEnumerator
– 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 IEnumerable
IEnumerable<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.
ECMA C# draft specification