КлассыClasses

Класс — это структура данных, которая может содержать элементы данных (константы и поля), функции-члены (методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, деструкторы и статические конструкторы) и вложенные типы.A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, destructors and static constructors), and nested types. Типы классов поддерживают наследование, механизм, в котором производный класс может расширять и специализацию базового класса.Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class.

Объявления классовClass declarations

Class_declaration — это type_declaration (объявления типов), объявляющий новый класс.A class_declaration is a type_declaration (Type declarations) that declares a new class.

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

Class_declaration состоит из необязательного набора атрибутов (атрибутов), за которым следует необязательный набор class_modifiers (модификаторы класса), за которым следует необязательный модификатор partial, за которым следует ключевое слово class и идентификатор , который именует класс, за которым следует необязательный type_parameter_list (параметр типа), за которым следуют Необязательная спецификация class_base (Базовая спецификация класса). по дополнительному набору type_parameter_constraints_clauses (ограничения параметра типа), за которым следует class_body (тело класса), при необходимости за точкой с запятой.A class_declaration consists of an optional set of attributes (Attributes), followed by an optional set of class_modifiers (Class modifiers), followed by an optional partial modifier, followed by the keyword class and an identifier that names the class, followed by an optional type_parameter_list (Type parameters), followed by an optional class_base specification (Class base specification) , followed by an optional set of type_parameter_constraints_clauses (Type parameter constraints), followed by a class_body (Class body), optionally followed by a semicolon.

Объявление класса не может предоставить type_parameter_constraints_clauses, если только он не предоставляет type_parameter_list.A class declaration cannot supply type_parameter_constraints_clauses unless it also supplies a type_parameter_list.

Объявление класса, предоставляющее type_parameter_list , является объявлением универсального класса.A class declaration that supplies a type_parameter_list is a generic class declaration. Кроме того, любой класс, вложенный в объявление универсального класса или универсальное объявление структуры, сам по себе является объявлением универсального класса, так как для создания сконструированного типа необходимо предоставить параметры типа для содержащего типа.Additionally, any class nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, since type parameters for the containing type must be supplied to create a constructed type.

Модификаторы классаClass modifiers

Class_declaration может дополнительно включать последовательность модификаторов класса:A class_declaration may optionally include a sequence of class modifiers:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | class_modifier_unsafe
    ;

Ошибка времени компиляции для того же модификатора встречается несколько раз в объявлении класса.It is a compile-time error for the same modifier to appear multiple times in a class declaration.

Модификатор new разрешен для вложенных классов.The new modifier is permitted on nested classes. Он указывает, что класс скрывает унаследованный член с тем же именем, как описано в модификаторе New.It specifies that the class hides an inherited member by the same name, as described in The new modifier. Если модификатор new отображается в объявлении класса, который не является объявлением вложенного класса, возникает ошибка времени компиляции.It is a compile-time error for the new modifier to appear on a class declaration that is not a nested class declaration.

Модификаторы public, protected, internalи private управляют специальными возможностями класса.The public, protected, internal, and private modifiers control the accessibility of the class. В зависимости от контекста, в котором происходит объявление класса, некоторые из этих модификаторов могут быть запрещены (объявленный уровень доступности).Depending on the context in which the class declaration occurs, some of these modifiers may not be permitted (Declared accessibility).

Модификаторы abstract, sealed и static обсуждаются в следующих разделах.The abstract, sealed and static modifiers are discussed in the following sections.

Абстрактные классыAbstract classes

Модификатор abstract используется для указания, что класс является неполным и предназначен для использования только в качестве базового класса.The abstract modifier is used to indicate that a class is incomplete and that it is intended to be used only as a base class. Абстрактный класс отличается от класса, не являющегося абстрактным, следующим образом.An abstract class differs from a non-abstract class in the following ways:

  • Абстрактный класс не может быть создан напрямую и является ошибкой времени компиляции для использования оператора new в абстрактном классе.An abstract class cannot be instantiated directly, and it is a compile-time error to use the new operator on an abstract class. Хотя возможно наличие переменных и значений, типы времени компиляции которых являются абстрактными, такие переменные и значения должны либо быть null, либо содержать ссылки на экземпляры неабстрактных классов, производных от абстрактных типов.While it is possible to have variables and values whose compile-time types are abstract, such variables and values will necessarily either be null or contain references to instances of non-abstract classes derived from the abstract types.
  • Абстрактный класс (но не обязательно) может содержать абстрактные члены.An abstract class is permitted (but not required) to contain abstract members.
  • Абстрактный класс не может быть запечатанным.An abstract class cannot be sealed.

Если неабстрактный класс является производным от абстрактного класса, то неабстрактный класс должен включать фактические реализации всех наследуемых абстрактных членов, тем самым переопределяя эти абстрактные члены.When a non-abstract class is derived from an abstract class, the non-abstract class must include actual implementations of all inherited abstract members, thereby overriding those abstract members. в примереIn the example

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
    }
}

Абстрактный класс A вводит абстрактный метод F.the abstract class A introduces an abstract method F. Класс B вводит дополнительный метод G, но поскольку он не предоставляет реализацию F, B также должен быть объявлен абстрактным.Class B introduces an additional method G, but since it doesn't provide an implementation of F, B must also be declared abstract. Класс C переопределяет F и предоставляет фактическую реализацию.Class C overrides F and provides an actual implementation. Поскольку в Cнет абстрактных элементов, C разрешается (но не требуется) как неабстрактные.Since there are no abstract members in C, C is permitted (but not required) to be non-abstract.

Запечатанные классыSealed classes

Модификатор sealed используется для предотвращения наследования от класса.The sealed modifier is used to prevent derivation from a class. Ошибка времени компиляции возникает, если запечатанный класс указан как базовый класс другого класса.A compile-time error occurs if a sealed class is specified as the base class of another class.

Запечатанный класс не может также быть абстрактным классом.A sealed class cannot also be an abstract class.

Модификатор sealed в основном используется для предотвращения непреднамеренного наследования, но также позволяет выполнять определенные оптимизации во время выполнения.The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain run-time optimizations. В частности, поскольку у запечатанного класса нет ни одного производного класса, вызовы членов виртуальных функций в запечатанных экземплярах класса можно преобразовать в вызовы, не являющиеся виртуальными.In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.

Статические классыStatic classes

Модификатор static используется для пометки класса, объявляемого как статический класс.The static modifier is used to mark the class being declared as a static class. Невозможно создать экземпляр статического класса, он не может использоваться как тип и может содержать только статические члены.A static class cannot be instantiated, cannot be used as a type and can contain only static members. Только статический класс может содержать объявления методов расширения (методов расширения).Only a static class can contain declarations of extension methods (Extension methods).

На объявление статического класса распространяются следующие ограничения.A static class declaration is subject to the following restrictions:

  • Статический класс не может включать модификатор sealed или abstract.A static class may not include a sealed or abstract modifier. Однако обратите внимание, что, поскольку статический класс не может создавать экземпляры или быть производным от него, он ведет себя так, как если бы он был запечатанным и абстрактным.Note, however, that since a static class cannot be instantiated or derived from, it behaves as if it was both sealed and abstract.
  • Статический класс не может включать спецификацию class_base (Базовая спецификация класса) и не может явно указывать базовый класс или список реализованных интерфейсов.A static class may not include a class_base specification (Class base specification) and cannot explicitly specify a base class or a list of implemented interfaces. Статический класс неявно наследуется от типа object.A static class implicitly inherits from type object.
  • Статический класс может содержать только статические члены (статические элементы и члены экземпляра).A static class can only contain static members (Static and instance members). Обратите внимание, что константы и вложенные типы классифицируются как статические члены.Note that constants and nested types are classified as static members.
  • Статический класс не может содержать члены с protected или protected internal объявленной доступности.A static class cannot have members with protected or protected internal declared accessibility.

Это ошибка времени компиляции, которая нарушает любые из этих ограничений.It is a compile-time error to violate any of these restrictions.

Статический класс не имеет конструкторов экземпляров.A static class has no instance constructors. Невозможно объявить конструктор экземпляра в статическом классе, а конструктор экземпляров по умолчанию (конструкторы по умолчанию) для статического класса не предусмотрен.It is not possible to declare an instance constructor in a static class, and no default instance constructor (Default constructors) is provided for a static class.

Члены статического класса не являются статическими автоматическими, а объявления членов должны явно включать модификатор static (за исключением констант и вложенных типов).The members of a static class are not automatically static, and the member declarations must explicitly include a static modifier (except for constants and nested types). Если класс вложен в статический внешний класс, вложенный класс не является статическим классом, если он явно не содержит модификатор static.When a class is nested within a static outer class, the nested class is not a static class unless it explicitly includes a static modifier.

Ссылки на статические типы классовReferencing static class types

Namespace_or_type_name (имена пространств имен и типов) может ссылаться на статический класс, еслиA namespace_or_type_name (Namespace and type names) is permitted to reference a static class if

  • Namespace_or_type_name — это T в namespace_or_type_name формы T.IилиThe namespace_or_type_name is the T in a namespace_or_type_name of the form T.I, or
  • Namespace_or_type_name — это T в typeof_expression (список аргументов1) в форме typeof(T).The namespace_or_type_name is the T in a typeof_expression (Argument lists1) of the form typeof(T).

Primary_expression (функции-члены) разрешено ссылаться на статический класс, еслиA primary_expression (Function members) is permitted to reference a static class if

В любом другом контексте это ошибка времени компиляции для ссылки на статический класс.In any other context it is a compile-time error to reference a static class. Например, является ошибкой использование статического класса в качестве базового класса, составного типа (вложенных типов) элемента, аргумента универсального типа или ограничения параметра типа.For example, it is an error for a static class to be used as a base class, a constituent type (Nested types) of a member, a generic type argument, or a type parameter constraint. Аналогично, статический класс не может использоваться в типе массива, типе указателя, new выражении, выражении приведения, выражении is, выражении as, выражении sizeof или выражении значения по умолчанию.Likewise, a static class cannot be used in an array type, a pointer type, a new expression, a cast expression, an is expression, an as expression, a sizeof expression, or a default value expression.

Модификатор partialPartial modifier

Модификатор partial используется, чтобы указать, что этот class_declaration является объявлением разделяемого типа.The partial modifier is used to indicate that this class_declaration is a partial type declaration. Несколько объявлений разделяемого типа с одним и тем же именем в пределах включающего пространства имен или объявления типа объединяются, образуя одно объявление типа, следуя правилам, заданным в разделяемых типах.Multiple partial type declarations with the same name within an enclosing namespace or type declaration combine to form one type declaration, following the rules specified in Partial types.

Объявление класса, распределенного по отдельным сегментам текста программы, может быть полезно, если эти сегменты создаются или поддерживаются в разных контекстах.Having the declaration of a class distributed over separate segments of program text can be useful if these segments are produced or maintained in different contexts. Например, одна часть объявления класса может быть создана компьютером, а другая — созданным вручную.For instance, one part of a class declaration may be machine generated, whereas the other is manually authored. Текстовое разделение двух элементов предотвращает обновление одного из конфликтующих обновлений другими.Textual separation of the two prevents updates by one from conflicting with updates by the other.

Параметры типаType parameters

Параметр типа — это простой идентификатор, который обозначает заполнитель для аргумента типа, предоставляемого для создания сконструированного типа.A type parameter is a simple identifier that denotes a placeholder for a type argument supplied to create a constructed type. Параметр типа — это формальный заполнитель для типа, который будет указан позже.A type parameter is a formal placeholder for a type that will be supplied later. Напротив, аргумент типа (аргументы типа) — это фактический тип, который подставляется для параметра типа при создании сконструированного типа.By contrast, a type argument (Type arguments) is the actual type that is substituted for the type parameter when a constructed type is created.

type_parameter_list
    : '<' type_parameters '>'
    ;

type_parameters
    : attributes? type_parameter
    | type_parameters ',' attributes? type_parameter
    ;

type_parameter
    : identifier
    ;

Каждый параметр типа в объявлении класса определяет имя в области объявления (объявления) этого класса.Each type parameter in a class declaration defines a name in the declaration space (Declarations) of that class. Таким образом, имя не может совпадать с именем другого параметра типа или члена, объявленного в этом классе.Thus, it cannot have the same name as another type parameter or a member declared in that class. Параметр типа не может иметь то же имя, что и сам тип.A type parameter cannot have the same name as the type itself.

Базовая спецификация классаClass base specification

Объявление класса может включать спецификацию class_base , которая определяет прямой базовый класс класса и интерфейсов (интерфейсов), непосредственно реализуемых классом.A class declaration may include a class_base specification, which defines the direct base class of the class and the interfaces (Interfaces) directly implemented by the class.

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

interface_type_list
    : interface_type (',' interface_type)*
    ;

Базовый класс, указанный в объявлении класса, может быть сконструированным типом класса (сконструированных типов).The base class specified in a class declaration can be a constructed class type (Constructed types). Базовый класс не может быть собственным параметром типа, хотя он может содержать параметры типа, настоящие в области.A base class cannot be a type parameter on its own, though it can involve the type parameters that are in scope.

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

базовых классов;Base classes

Когда class_type включается в class_base, он указывает прямой базовый класс для объявляемого класса.When a class_type is included in the class_base, it specifies the direct base class of the class being declared. Если объявление класса не имеет class_baseили если class_base перечисляет только типы интерфейсов, предполагается, что прямой базовый класс является object.If a class declaration has no class_base, or if the class_base lists only interface types, the direct base class is assumed to be object. Класс наследует члены от своего прямого базового класса, как описано в разделе наследование.A class inherits members from its direct base class, as described in Inheritance.

в примереIn the example

class A {}

class B: A {}

класс A называется прямым базовым классом B, а B считается производным от A.class A is said to be the direct base class of B, and B is said to be derived from A. Поскольку A явно не указывает прямой базовый класс, его прямой базовый класс неявно object.Since A does not explicitly specify a direct base class, its direct base class is implicitly object.

Для сконструированного типа класса, если базовый класс указан в объявлении универсального класса, базовый класс сконструированного типа получается путем подстановки для каждого type_parameter в объявлении базового класса, соответствующего type_argument сконструированного типа.For a constructed class type, if a base class is specified in the generic class declaration, the base class of the constructed type is obtained by substituting, for each type_parameter in the base class declaration, the corresponding type_argument of the constructed type. С учетом объявлений универсального классаGiven the generic class declarations

class B<U,V> {...}

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

базовый класс сконструированного типа G<int> будет B<string,int[]>.the base class of the constructed type G<int> would be B<string,int[]>.

Прямой базовый класс типа класса должен иметь по крайней мере такой же уровень доступности, как и сам тип класса (Домены доступности).The direct base class of a class type must be at least as accessible as the class type itself (Accessibility domains). Например, ошибка времени компиляции возникает, когда класс public является производным от класса private или internal.For example, it is a compile-time error for a public class to derive from a private or internal class.

Прямой базовый класс для типа класса не должен быть одним из следующих типов: System.Array, System.Delegate, System.MulticastDelegate, System.Enumили System.ValueType.The direct base class of a class type must not be any of the following types: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, or System.ValueType. Более того, объявление универсального класса не может использовать System.Attribute как прямой или косвенный базовый класс.Furthermore, a generic class declaration cannot use System.Attribute as a direct or indirect base class.

При определении значения прямой спецификации базового класса A класса B, прямой базовый класс B временно предполагается object.While determining the meaning of the direct base class specification A of a class B, the direct base class of B is temporarily assumed to be object. Интуитивно это гарантирует, что значение спецификации базового класса не может рекурсивно зависеть от себя.Intuitively this ensures that the meaning of a base class specification cannot recursively depend on itself. Пример:The example:

class A<T> {
   public class B {}
}

class C : A<C.B> {}

является ошибкой из-за того, что в спецификации базового класса A<C.B> прямой базовый класс C считается objectным, и поэтому (по правилам пространства имен и имен типов) C не считается членом B.is in error since in the base class specification A<C.B> the direct base class of C is considered to be object, and hence (by the rules of Namespace and type names) C is not considered to have a member B.

Базовые классы типа класса являются прямым базовым классом и его базовыми классами.The base classes of a class type are the direct base class and its base classes. Иными словами, набор базовых классов является транзитивным замыканием связи прямого базового класса.In other words, the set of base classes is the transitive closure of the direct base class relationship. В приведенном выше примере базовыми классами B являются A и object.Referring to the example above, the base classes of B are A and object. в примереIn the example

class A {...}

class B<T>: A {...}

class C<T>: B<IComparable<T>> {...}

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

базовыми классами D<int> являются C<int[]>, B<IComparable<int[]>>, Aи object.the base classes of D<int> are C<int[]>, B<IComparable<int[]>>, A, and object.

За исключением класса object, каждый тип класса имеет только один прямой базовый класс.Except for class object, every class type has exactly one direct base class. Класс object не имеет прямого базового класса и является конечным базовым классом для всех других классов.The object class has no direct base class and is the ultimate base class of all other classes.

Когда класс B является производным от класса A, он является ошибкой времени компиляции, чтобы A зависела от B.When a class B derives from a class A, it is a compile-time error for A to depend on B. Класс непосредственно зависит от его прямого базового класса (если он есть) и непосредственно зависит от класса, внутри которого он непосредственно вложен (если он есть).A class directly depends on its direct base class (if any) and directly depends on the class within which it is immediately nested (if any). Учитывая это определение, полный набор классов, от которых зависит класс, является перегибким и транзитивным замыканием непосредственно зависит от связи.Given this definition, the complete set of classes upon which a class depends is the reflexive and transitive closure of the directly depends on relationship.

ПримерThe example

class A: A {}

является ошибочным, поскольку класс зависит от самого себя.is erroneous because the class depends on itself. Аналогичным образом, примерLikewise, the example

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

является ошибкой, так как классы циклически зависят от себя.is in error because the classes circularly depend on themselves. Наконец, в примереFinally, the example

class A: B.C {}

class B: A
{
    public class C {}
}

приводит к ошибке во время компиляции, так как A зависит от B.C (его прямым базовым классом), который зависит от B (его непосредственно включающего класса), который циклически зависит от A.results in a compile-time error because A depends on B.C (its direct base class), which depends on B (its immediately enclosing class), which circularly depends on A.

Обратите внимание, что класс не зависит от классов, вложенных в него.Note that a class does not depend on the classes that are nested within it. в примереIn the example

class A
{
    class B: A {}
}

B зависит от A (поскольку A является как прямым базовым классом, так и его непосредственно включающим классом), но A не зависит от B (поскольку B не является ни базовым классом, ни включающим классом A).B depends on A (because A is both its direct base class and its immediately enclosing class), but A does not depend on B (since B is neither a base class nor an enclosing class of A). Таким образом, пример допустим.Thus, the example is valid.

Невозможно выполнить наследование от класса sealed.It is not possible to derive from a sealed class. в примереIn the example

sealed class A {}

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

B класса имеет ошибку, так как он пытается наследовать от класса sealed A.class B is in error because it attempts to derive from the sealed class A.

Реализации интерфейсовInterface implementations

Спецификация class_base может включать в себя список типов интерфейсов, в этом случае класс называется непосредственной реализацией заданных типов интерфейса.A class_base specification may include a list of interface types, in which case the class is said to directly implement the given interface types. Реализации интерфейсов обсуждаются далее в реализациях интерфейса.Interface implementations are discussed further in Interface implementations.

Ограничения параметров типаType parameter constraints

Объявления универсального типа и метода могут дополнительно задавать ограничения параметров типа, включая type_parameter_constraints_clauses.Generic type and method declarations can optionally specify type parameter constraints by including type_parameter_constraints_clauses.

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint
    | secondary_constraints
    | constructor_constraint
    | primary_constraint ',' secondary_constraints
    | primary_constraint ',' constructor_constraint
    | secondary_constraints ',' constructor_constraint
    | primary_constraint ',' secondary_constraints ',' constructor_constraint
    ;

primary_constraint
    : class_type
    | 'class'
    | 'struct'
    ;

secondary_constraints
    : interface_type
    | type_parameter
    | secondary_constraints ',' interface_type
    | secondary_constraints ',' type_parameter
    ;

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

Каждая type_parameter_constraints_clause состоит из маркера where, за которым следует имя параметра типа, двоеточие и список ограничений для этого параметра типа.Each type_parameter_constraints_clause consists of the token where, followed by the name of a type parameter, followed by a colon and the list of constraints for that type parameter. Для каждого параметра типа может существовать не более одного предложения where, а предложения where могут быть перечислены в любом порядке.There can be at most one where clause for each type parameter, and the where clauses can be listed in any order. Как и токены get и set в методе доступа к свойству, маркер where не является ключевым словом.Like the get and set tokens in a property accessor, the where token is not a keyword.

Список ограничений, заданных в предложении where, может включать любые из следующих компонентов в следующем порядке: одно основное ограничение, одно или несколько вторичных ограничений, а также ограничение конструктора, new().The list of constraints given in a where clause can include any of the following components, in this order: a single primary constraint, one or more secondary constraints, and the constructor constraint, new().

Основное ограничение может быть типом класса или ограничением ссылочного типа class или ограничением типа значения struct.A primary constraint can be a class type or the reference type constraint class or the value type constraint struct. Вторичным ограничением может быть type_parameter или interface_type.A secondary constraint can be a type_parameter or interface_type.

Ограничение ссылочного типа указывает, что аргумент типа, используемый для параметра типа, должен быть ссылочным типом.The reference type constraint specifies that a type argument used for the type parameter must be a reference type. Все типы классов, типы интерфейсов, типы делегатов, типы массивов и параметры типа, известные как ссылочный тип (как определено ниже), соответствуют этому ограничению.All class types, interface types, delegate types, array types, and type parameters known to be a reference type (as defined below) satisfy this constraint.

Ограничение типа значения указывает, что аргумент типа, используемый для параметра типа, должен быть типом значения, не допускающим значения NULL.The value type constraint specifies that a type argument used for the type parameter must be a non-nullable value type. Все типы структур, не допускающие значения NULL, типы Enum и параметры типа с ограничением типа значения соответствуют этому ограничению.All non-nullable struct types, enum types, and type parameters having the value type constraint satisfy this constraint. Обратите внимание, что, несмотря на то, что классифицировано как тип значения, тип, допускающий значение null (типы Nullable), не удовлетворяет ограничению типа значения.Note that although classified as a value type, a nullable type (Nullable types) does not satisfy the value type constraint. Параметр типа, имеющий ограничение типа значения, не может также иметь constructor_constraint.A type parameter having the value type constraint cannot also have the constructor_constraint.

Типы указателей никогда не могут быть аргументами типа и не соответствуют ограничениям ссылочного типа или типа значения.Pointer types are never allowed to be type arguments and are not considered to satisfy either the reference type or value type constraints.

Если ограничение является типом класса, типом интерфейса или параметром типа, этот тип задает минимальный "базовый тип", который должен поддерживать каждый аргумент типа, используемый для этого параметра типа.If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal "base type" that every type argument used for that type parameter must support. При использовании сконструированного типа или универсального метода аргумент типа проверяется на соответствие ограничениям параметра типа во время компиляции.Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile-time. Указанный аргумент типа должен соответствовать условиям, описанным в разделе соблюдение ограничений.The type argument supplied must satisfy the conditions described in Satisfying constraints.

Ограничение class_type должно соответствовать следующим правилам.A class_type constraint must satisfy the following rules:

  • Тип должен быть типом класса.The type must be a class type.
  • Тип не должен быть sealed.The type must not be sealed.
  • Тип не должен быть одним из следующих типов: System.Array, System.Delegate, System.Enumили System.ValueType.The type must not be one of the following types: System.Array, System.Delegate, System.Enum, or System.ValueType.
  • Тип не должен быть object.The type must not be object. Поскольку все типы являются производными от object, такое ограничение не будет действовать, если оно было разрешено.Because all types derive from object, such a constraint would have no effect if it were permitted.
  • Не более одного ограничения для данного параметра типа может быть типом класса.At most one constraint for a given type parameter can be a class type.

Тип, указанный в качестве ограничения interface_type , должен соответствовать следующим правилам.A type specified as an interface_type constraint must satisfy the following rules:

  • Тип должен быть типом интерфейса.The type must be an interface type.
  • Тип не должен быть указан более одного раза в данном предложении where.A type must not be specified more than once in a given where clause.

В любом случае ограничение может затрагивать любой из параметров типа в объявлении связанного типа или метода как часть сконструированного типа и может содержать объявляемый тип.In either case, the constraint can involve any of the type parameters of the associated type or method declaration as part of a constructed type, and can involve the type being declared.

Любой тип класса или интерфейса, указанный в качестве ограничения параметра типа, должен быть по крайней мере доступным (ограничения доступности) в качестве объявляемого универсального типа или метода.Any class or interface type specified as a type parameter constraint must be at least as accessible (Accessibility constraints) as the generic type or method being declared.

Тип, указанный в качестве ограничения type_parameter , должен соответствовать следующим правилам.A type specified as a type_parameter constraint must satisfy the following rules:

  • Тип должен быть параметром типа.The type must be a type parameter.
  • Тип не должен быть указан более одного раза в данном предложении where.A type must not be specified more than once in a given where clause.

Кроме того, в графе зависимостей в параметрах типа не должно быть циклов, где зависимость — это транзитивное отношение, определяемое:In addition there must be no cycles in the dependency graph of type parameters, where dependency is a transitive relation defined by:

  • Если параметр типа T используется в качестве ограничения для параметра типа S то S зависит от T.If a type parameter T is used as a constraint for type parameter S then S depends on T.
  • Если параметр типа S зависит от параметра типа T и T зависит от параметра типа U то S зависит от U.If a type parameter S depends on a type parameter T and T depends on a type parameter U then S depends on U.

Учитывая это отношение, ошибка времени компиляции, когда параметр типа должен зависеть от себя (прямо или косвенно).Given this relation, it is a compile-time error for a type parameter to depend on itself (directly or indirectly).

Все ограничения должны быть согласованными между зависимыми параметрами типа.Any constraints must be consistent among dependent type parameters. Если параметр типа S зависит от параметра типа T то:If type parameter S depends on type parameter T then:

  • T не должно иметь ограничения типа значения.T must not have the value type constraint. В противном случае T фактически запечатан, поэтому S будет иметь тот же тип, что и T, устраняя необходимость в двух параметрах типа.Otherwise, T is effectively sealed so S would be forced to be the same type as T, eliminating the need for two type parameters.
  • Если S имеет ограничение типа значения, T не должно иметь ограничения class_type .If S has the value type constraint then T must not have a class_type constraint.
  • Если S имеет ограничение class_type A а T имеет class_typeное ограничение B то необходимо преобразование удостоверения или неявное преобразование ссылки из A в B или неявное преобразование ссылки из B в A.If S has a class_type constraint A and T has a class_type constraint B then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.
  • Если S также зависит от параметра типа U а U имеет ограничение class_type A и T имеет class_typeное ограничение B то необходимо преобразование удостоверения или неявное преобразование ссылки из A в B или неявное преобразование ссылки из B в A.If S also depends on type parameter U and U has a class_type constraint A and T has a class_type constraint B then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.

Допустимость для S имеет ограничение типа значения и T, чтобы иметь ограничение ссылочного типа.It is valid for S to have the value type constraint and T to have the reference type constraint. Фактически это ограничивает T типов System.Object, System.ValueType, System.Enumи любого типа интерфейса.Effectively this limits T to the types System.Object, System.ValueType, System.Enum, and any interface type.

Если предложение where для параметра типа включает ограничение конструктора (имеющее форму new()), можно использовать оператор new для создания экземпляров типа (выражения для создания объектов).If the where clause for a type parameter includes a constructor constraint (which has the form new()), it is possible to use the new operator to create instances of the type (Object creation expressions). Любой аргумент типа, используемый для параметра типа с ограничением конструктора, должен иметь открытый конструктор без параметров (этот конструктор неявно существует для любого типа значения) или быть параметром типа с ограничением типа значения или ограничения конструктора (Дополнительные сведения см. в разделе ограничения на параметры типа ).Any type argument used for a type parameter with a constructor constraint must have a public parameterless constructor (this constructor implicitly exists for any value type) or be a type parameter having the value type constraint or constructor constraint (see Type parameter constraints for details).

Ниже приведены примеры ограничений.The following are examples of constraints:

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()
{
    ...
}

Следующий пример приводит к ошибке, так как вызывает цикличность в графе зависимостей параметров типа:The following example is in error because it causes a circularity in the dependency graph of the type parameters:

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

В следующих примерах показаны дополнительные недопустимые ситуации.The following examples illustrate additional invalid situations:

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
{
    ...
}

Эффективный базовый класс параметра типа T определяется следующим образом:The effective base class of a type parameter T is defined as follows:

  • Если T не имеет основных ограничений или ограничений параметра типа, его действующий базовый класс будет object.If T has no primary constraints or type parameter constraints, its effective base class is object.
  • Если T имеет ограничение типа значения, его действующий базовый класс System.ValueType.If T has the value type constraint, its effective base class is System.ValueType.
  • Если T имеет ограничение class_type C но не type_parameter ограничения, его действующий базовый класс C.If T has a class_type constraint C but no type_parameter constraints, its effective base class is C.
  • Если T не имеет ограничения class_type , но имеет одно или несколько ограничений type_parameter , его эффективный базовый класс является наиболее охватываемым типом (Операторы преобразования с нулификацией) в наборе действующих базовых классов его ограничений type_parameter .If T has no class_type constraint but has one or more type_parameter constraints, its effective base class is the most encompassed type (Lifted conversion operators) in the set of effective base classes of its type_parameter constraints. Правила согласованности гарантируют, что такой наиболее охватываемый тип существует.The consistency rules ensure that such a most encompassed type exists.
  • Если T имеет как ограничение class_type , так и одно или несколько ограничений type_parameter , его эффективный базовый класс является наиболее охватываемым типом (Операторы с нулификацией) в наборе, состоящим из ограничения class_type T и действующих базовых классов его ограничений type_parameter .If T has both a class_type constraint and one or more type_parameter constraints, its effective base class is the most encompassed type (Lifted conversion operators) in the set consisting of the class_type constraint of T and the effective base classes of its type_parameter constraints. Правила согласованности гарантируют, что такой наиболее охватываемый тип существует.The consistency rules ensure that such a most encompassed type exists.
  • Если T имеет ограничение ссылочного типа, но не имеет ограничений class_type , его действующий базовый класс — object.If T has the reference type constraint but no class_type constraints, its effective base class is object.

Для целей этих правил, если T имеет ограничение V которое является value_type, используйте вместо него наиболее конкретный базовый тип V, который является class_type.For the purpose of these rules, if T has a constraint V that is a value_type, use instead the most specific base type of V that is a class_type. Это не всегда происходит в явно заданном ограничении, но может произойти, если ограничения универсального метода неявно наследуются объявлением метода переопределения или явной реализацией метода интерфейса.This can never happen in an explicitly given constraint, but may occur when the constraints of a generic method are implicitly inherited by an overriding method declaration or an explicit implementation of an interface method.

Эти правила гарантируют, что действующий базовый класс всегда является class_type.These rules ensure that the effective base class is always a class_type.

Эффективный набор интерфейсов параметра типа T определяется следующим образом:The effective interface set of a type parameter T is defined as follows:

  • Если T не имеет secondary_constraints, его действующий набор интерфейсов пуст.If T has no secondary_constraints, its effective interface set is empty.
  • Если T имеет ограничения interface_type , но не имеет ограничений type_parameter , его действующий набор интерфейсов является набором ограничений interface_type .If T has interface_type constraints but no type_parameter constraints, its effective interface set is its set of interface_type constraints.
  • Если T не имеет ограничений interface_type , но имеет ограничения type_parameter , его действующий набор интерфейсов является объединением эффективных наборов интерфейсов его ограничений type_parameter .If T has no interface_type constraints but has type_parameter constraints, its effective interface set is the union of the effective interface sets of its type_parameter constraints.
  • Если T имеет как ограничения interface_type , так и ограничения type_parameter , его действующий набор интерфейсов является объединением набора ограничений interface_type и эффективных наборов интерфейсов его ограничений type_parameter .If T has both interface_type constraints and type_parameter constraints, its effective interface set is the union of its set of interface_type constraints and the effective interface sets of its type_parameter constraints.

Параметр типа называется ссылочным типом , если он имеет ограничение ссылочного типа, или его действующий базовый класс не object или System.ValueType.A type parameter is known to be a reference type if it has the reference type constraint or its effective base class is not object or System.ValueType.

Значения ограниченного типа параметра типа можно использовать для доступа к членам экземпляра, подразумеваемым ограничениями.Values of a constrained type parameter type can be used to access the instance members implied by the constraints. в примереIn the example

interface IPrintable
{
    void Print();
}

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

методы IPrintable могут вызываться непосредственно в x, так как T ограничена реализацией IPrintable.the methods of IPrintable can be invoked directly on x because T is constrained to always implement IPrintable.

Тело классаClass body

Class_body класса определяет элементы этого класса.The class_body of a class defines the members of that class.

class_body
    : '{' class_member_declaration* '}'
    ;

Разделяемые типыPartial types

Объявление типа можно разделить на несколько объявлений частичного типа.A type declaration can be split across multiple partial type declarations. Объявление типа создается из его частей в соответствии с правилами, приведенными в этом разделе, после чего он рассматривается как единое объявление в течение оставшейся части времени компиляции и обработки программы во время выполнения.The type declaration is constructed from its parts by following the rules in this section, whereupon it is treated as a single declaration during the remainder of the compile-time and run-time processing of the program.

Class_declaration, struct_declaration или interface_declaration представляет объявление разделяемого типа, если оно содержит модификатор partial.A class_declaration, struct_declaration or interface_declaration represents a partial type declaration if it includes a partial modifier. partial не является ключевым словом и действует как модификатор, только если он встречается непосредственно перед одним из ключевых слов class, struct или interface в объявлении типа или перед типом void в объявлении метода.partial is not a keyword, and only acts as a modifier if it appears immediately before one of the keywords class, struct or interface in a type declaration, or before the type void in a method declaration. В других контекстах его можно использовать в качестве обычного идентификатора.In other contexts it can be used as a normal identifier.

Каждая часть объявления разделяемого типа должна включать модификатор partial.Each part of a partial type declaration must include a partial modifier. Оно должно иметь то же имя и быть объявлено в том же пространстве имен или объявлении типа, что и другие части.It must have the same name and be declared in the same namespace or type declaration as the other parts. Модификатор partial указывает, что дополнительные части объявления типа могут существовать в других местах, но существование таких дополнительных частей не является обязательным. тип с одним объявлением является допустимым для включения модификатора partial.The partial modifier indicates that additional parts of the type declaration may exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for a type with a single declaration to include the partial modifier.

Все части разделяемого типа должны быть скомпилированы вместе, чтобы во время компиляции части можно было объединить в одно объявление типа.All parts of a partial type must be compiled together such that the parts can be merged at compile-time into a single type declaration. Разделяемые типы специально не допускают расширение уже скомпилированных типов.Partial types specifically do not allow already compiled types to be extended.

Вложенные типы могут быть объявлены в нескольких частях с помощью модификатора partial.Nested types may be declared in multiple parts by using the partial modifier. Как правило, вмещающий тип объявляется с помощью partial, а каждая часть вложенного типа объявляется в другой части содержащего типа.Typically, the containing type is declared using partial as well, and each part of the nested type is declared in a different part of the containing type.

Модификатор partial не разрешен в объявлениях делегата или перечисления.The partial modifier is not permitted on delegate or enum declarations.

АтрибутыAttributes

Атрибуты разделяемого типа определяются путем объединения в неопределенном порядке атрибутов каждой части.The attributes of a partial type are determined by combining, in an unspecified order, the attributes of each of the parts. Если атрибут размещается в нескольких частях, то он эквивалентен указанию атрибута в типе несколько раз.If an attribute is placed on multiple parts, it is equivalent to specifying the attribute multiple times on the type. Например, две части:For example, the two parts:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

эквивалентны объявлению:are equivalent to a declaration such as:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

Атрибуты в параметрах типа объединяются аналогичным образом.Attributes on type parameters combine in a similar fashion.

МодификаторыModifiers

Если объявление разделяемого типа включает спецификацию доступности (модификаторы public, protected, internalи private), оно должно быть согласовано со всеми остальными частями, включающими спецификацию доступности.When a partial type declaration includes an accessibility specification (the public, protected, internal, and private modifiers) it must agree with all other parts that include an accessibility specification. Если ни одна часть разделяемого типа не включает спецификацию доступности, тип получает соответствующий уровень доступности по умолчанию (объявленный уровень доступности).If no part of a partial type includes an accessibility specification, the type is given the appropriate default accessibility (Declared accessibility).

Если одно или несколько частичных объявлений вложенного типа содержат модификатор new, предупреждение не выводится, если вложенный тип скрывает унаследованный член (скрывая через наследование).If one or more partial declarations of a nested type include a new modifier, no warning is reported if the nested type hides an inherited member (Hiding through inheritance).

Если одно или несколько частичных объявлений класса включают модификатор abstract, класс считается абстрактным (абстрактные классы).If one or more partial declarations of a class include an abstract modifier, the class is considered abstract (Abstract classes). В противном случае класс считается неабстрактным.Otherwise, the class is considered non-abstract.

Если одно или несколько частичных объявлений класса включают модификатор sealed, класс считается запечатанным (запечатанными классами).If one or more partial declarations of a class include a sealed modifier, the class is considered sealed (Sealed classes). В противном случае класс считается незапечатанным.Otherwise, the class is considered unsealed.

Обратите внимание, что класс не может быть одновременно абстрактным и запечатанным.Note that a class cannot be both abstract and sealed.

Если модификатор unsafe используется в объявлении разделяемого типа, только эта конкретная часть считается небезопасным контекстом (небезопаснымиконтекста).When the unsafe modifier is used on a partial type declaration, only that particular part is considered an unsafe context (Unsafe contexts).

Параметры и ограничения типаType parameters and constraints

Если универсальный тип объявлен в нескольких частях, каждая часть должна заменять параметры типа.If a generic type is declared in multiple parts, each part must state the type parameters. Каждая часть должна иметь одинаковое число параметров типа и одинаковое имя для каждого параметра типа, по порядку.Each part must have the same number of type parameters, and the same name for each type parameter, in order.

Если разделяемое объявление универсального типа содержит ограничения (where предложения), ограничения должны быть согласованы со всеми остальными частями, которые включают ограничения.When a partial generic type declaration includes constraints (where clauses), the constraints must agree with all other parts that include constraints. В частности, каждая часть, которая включает ограничения, должна иметь ограничения для одного и того же набора параметров типа, и для каждого параметра типа наборы первичных, вторичных и конструкторных ограничений должны быть эквивалентны.Specifically, each part that includes constraints must have constraints for the same set of type parameters, and for each type parameter the sets of primary, secondary, and constructor constraints must be equivalent. Два набора ограничений эквивалентны, если они содержат одинаковые члены.Two sets of constraints are equivalent if they contain the same members. Если ни одна часть разделяемого универсального типа не задает ограничения параметра типа, параметры типа считаются неограниченными.If no part of a partial generic type specifies type parameter constraints, the type parameters are considered unconstrained.

ПримерThe example

partial class Dictionary<K,V>
    where K: IComparable<K>
    where V: IKeyProvider<K>, IPersistable
{
    ...
}

partial class Dictionary<K,V>
    where V: IPersistable, IKeyProvider<K>
    where K: IComparable<K>
{
    ...
}

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

является верным, поскольку в этих частях, содержащих ограничения (первые два), фактически указывается один и тот же набор первичных, вторичных и конструкторных ограничений для одного и того же набора параметров типа соответственно.is correct because those parts that include constraints (the first two) effectively specify the same set of primary, secondary, and constructor constraints for the same set of type parameters, respectively.

Базовый классBase class

Если объявление разделяемого класса включает спецификацию базового класса, оно должно быть согласовано со всеми остальными частями, включающими спецификацию базового класса.When a partial class declaration includes a base class specification it must agree with all other parts that include a base class specification. Если ни одна часть разделяемого класса не содержит спецификацию базового класса, то базовый класс станет System.Object (базовые классы).If no part of a partial class includes a base class specification, the base class becomes System.Object (Base classes).

Базовые интерфейсыBase interfaces

Набор базовых интерфейсов для типа, объявленного в нескольких частях, является объединением базовых интерфейсов, указанных в каждой части.The set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. Конкретный базовый интерфейс может называться только один раз в каждой части, но несколько частей могут называть одни и те же базовые интерфейсы.A particular base interface may only be named once on each part, but it is permitted for multiple parts to name the same base interface(s). Должна быть только одна реализация членов любого заданного базового интерфейса.There must only be one implementation of the members of any given base interface.

в примереIn the example

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

partial class C: IC {...}

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

набор базовых интерфейсов для класса CIA, IBи IC.the set of base interfaces for class C is IA, IB, and IC.

Как правило, каждая часть предоставляет реализацию интерфейсов, объявленных в этой части; Однако это не обязательно.Typically, each part provides an implementation of the interface(s) declared on that part; however, this is not a requirement. Часть может предоставить реализацию для интерфейса, объявленного в другой части:A part may provide the implementation for an interface declared on a different part:

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

partial class X: IComparable
{
    ...
}

MembersMembers

За исключением разделяемых методов (разделяемых методов), набор членов типа, объявленного в нескольких частях, является просто объединением набора членов, объявленных в каждой части.With the exception of partial methods (Partial methods), the set of members of a type declared in multiple parts is simply the union of the set of members declared in each part. Тела всех частей объявления типа совместно используют одно и то же пространство объявления (объявления), а область каждого элемента (областей) расширяется до тела всех частей.The bodies of all parts of the type declaration share the same declaration space (Declarations), and the scope of each member (Scopes) extends to the bodies of all the parts. Домен доступности любого члена всегда включает все части включающего типа; член private, объявленный в одной части, свободно доступен из другой части.The accessibility domain of any member always includes all the parts of the enclosing type; a private member declared in one part is freely accessible from another part. Объявление одного и того же члена более чем в одной части типа, если этот член не является типом с модификатором partial, является ошибкой времени компиляции.It is a compile-time error to declare the same member in more than one part of the type, unless that member is a type with the partial modifier.

partial class A
{
    int x;                     // Error, cannot declare x more than once

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

partial class A
{
    int x;                     // Error, cannot declare x more than once

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

Порядок членов в типе редко важен для C# кода, но может быть значительным при взаимоработе с другими языками и средами.The ordering of members within a type is rarely significant to C# code, but may be significant when interfacing with other languages and environments. В таких случаях порядок элементов в типе, объявленном в нескольких частях, не определен.In these cases, the ordering of members within a type declared in multiple parts is undefined.

Разделяемые методыPartial methods

Разделяемые методы могут быть определены в одной части объявления типа и реализованы в другом.Partial methods can be defined in one part of a type declaration and implemented in another. Реализация является необязательной. Если ни одна часть не реализует разделяемый метод, объявление разделяемого метода и все его вызовы удаляются из объявления типа, полученного в результате сочетания частей.The implementation is optional; if no part implements the partial method, the partial method declaration and all calls to it are removed from the type declaration resulting from the combination of the parts.

Разделяемые методы не могут определять модификаторы доступа, но неявно private.Partial methods cannot define access modifiers, but are implicitly private. Их возвращаемый тип должен быть void, а их параметры не могут иметь модификатор out.Their return type must be void, and their parameters cannot have the out modifier. Идентификатор partial распознается как специальное ключевое слово в объявлении метода только в том случае, если он отображается непосредственно перед типом void; в противном случае его можно использовать в качестве обычного идентификатора.The identifier partial is recognized as a special keyword in a method declaration only if it appears right before the void type; otherwise it can be used as a normal identifier. Разделяемый метод не может явно реализовывать методы интерфейса.A partial method cannot explicitly implement interface methods.

Существует два вида объявлений разделяемых методов: если тело объявления метода является точкой с запятой, объявление считается определяющим объявлением разделяемого метода.There are two kinds of partial method declarations: If the body of the method declaration is a semicolon, the declaration is said to be a defining partial method declaration. Если тело задано как блок, объявление считается реализующим объявлением разделяемого метода.If the body is given as a block, the declaration is said to be an implementing partial method declaration. В частях объявления типа может существовать только одно определение разделяемого метода с заданной сигнатурой, и может существовать только одна реализация разделяемого метода с заданной сигнатурой.Across the parts of a type declaration there can be only one defining partial method declaration with a given signature, and there can be only one implementing partial method declaration with a given signature. Если указано реализующее объявление разделяемого метода, должно существовать соответствующее определяющее объявление разделяемого метода, и объявления должны соответствовать следующим образом:If an implementing partial method declaration is given, a corresponding defining partial method declaration must exist, and the declarations must match as specified in the following:

  • Объявления должны иметь одни и те же модификаторы (хотя и не обязательно в том же порядке), имя метода, число параметров типа и количество параметров.The declarations must have the same modifiers (although not necessarily in the same order), method name, number of type parameters and number of parameters.
  • Соответствующие параметры в объявлениях должны иметь одни и те же модификаторы (хотя и не обязательно в том же порядке) и одни и те же типы (отличия по модулю в именах параметров типов).Corresponding parameters in the declarations must have the same modifiers (although not necessarily in the same order) and the same types (modulo differences in type parameter names).
  • Соответствующие параметры типа в объявлениях должны иметь одинаковые ограничения (различия по модулю в именах параметров типов).Corresponding type parameters in the declarations must have the same constraints (modulo differences in type parameter names).

Реализующее объявление разделяемого метода может находиться в той же части, что и соответствующее объявление разделяемого метода.An implementing partial method declaration can appear in the same part as the corresponding defining partial method declaration.

Только определяющий разделяемый метод участвует в разрешении перегрузки.Only a defining partial method participates in overload resolution. Таким способом, независимо от того, задано ли реализующее объявление, выражения вызова могут разрешать вызовы разделяемого метода.Thus, whether or not an implementing declaration is given, invocation expressions may resolve to invocations of the partial method. Поскольку разделяемый метод всегда возвращает void, такие выражения вызова всегда будут операторами выражений.Because a partial method always returns void, such invocation expressions will always be expression statements. Более того, поскольку разделяемый метод неявно private, такие операторы всегда будут находиться в одной из частей объявления типа, в рамках которого объявлен разделяемый метод.Furthermore, because a partial method is implicitly private, such statements will always occur within one of the parts of the type declaration within which the partial method is declared.

Если ни одна часть объявления разделяемого типа не содержит реализующее объявление для данного разделяемого метода, любой оператор выражения, вызывающий его, просто удаляется из комбинированного объявления типа.If no part of a partial type declaration contains an implementing declaration for a given partial method, any expression statement invoking it is simply removed from the combined type declaration. Поэтому выражение вызова, включая любые составляющие выражения, не оказывает влияния на время выполнения.Thus the invocation expression, including any constituent expressions, has no effect at run-time. Сам разделяемый метод также удаляется и не будет членом комбинированного объявления типа.The partial method itself is also removed and will not be a member of the combined type declaration.

Если для данного разделяемого метода существует реализующее объявление, то вызовы разделяемых методов сохраняются.If an implementing declaration exist for a given partial method, the invocations of the partial methods are retained. Разделяемый метод передает объявление метода, аналогичное объявлению разделяемого метода, за исключением следующих:The partial method gives rise to a method declaration similar to the implementing partial method declaration except for the following:

  • Модификатор partial не включенThe partial modifier is not included
  • Атрибуты в объявлении результирующего метода — это Объединенные атрибуты определяющего и реализующего объявление разделяемого метода в неопределенном порядке.The attributes in the resulting method declaration are the combined attributes of the defining and the implementing partial method declaration in unspecified order. Дубликаты не удаляются.Duplicates are not removed.
  • Атрибуты параметров в объявлении результирующего метода являются объединенными атрибутами соответствующих параметров определяющего и реализующего объявление разделяемого метода в неопределенном порядке.The attributes on the parameters of the resulting method declaration are the combined attributes of the corresponding parameters of the defining and the implementing partial method declaration in unspecified order. Дубликаты не удаляются.Duplicates are not removed.

Если для разделяемого метода M указано определяющее объявление, но не реализующее объявление, применяются следующие ограничения.If a defining declaration but not an implementing declaration is given for a partial method M, the following restrictions apply:

Разделяемые методы полезны для того, чтобы разрешить одной части объявления типа настраивать поведение другой части, например, созданной средством.Partial methods are useful for allowing one part of a type declaration to customize the behavior of another part, e.g., one that is generated by a tool. Рассмотрим следующее объявление разделяемого класса:Consider the following partial class declaration:

partial class Customer
{
    string name;

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

    }

    partial void OnNameChanging(string newName);

    partial void OnNameChanged();
}

Если этот класс компилируется без каких-либо других частей, объявления разделяемого метода и их вызовы будут удалены, а полученное комбинированное объявление класса будет эквивалентно следующему:If this class is compiled without any other parts, the defining partial method declarations and their invocations will be removed, and the resulting combined class declaration will be equivalent to the following:

class Customer
{
    string name;

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

Предположим, что указана другая часть, которая предоставляет реализацию объявлений разделяемых методов:Assume that another part is given, however, which provides implementing declarations of the partial methods:

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

    partial void OnNameChanged()
    {
        Console.WriteLine("Changed to " + name);
    }
}

Затем полученное комбинированное объявление класса будет эквивалентно следующему:Then the resulting combined class declaration will be equivalent to the following:

class Customer
{
    string name;

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

    }

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

    void OnNameChanged()
    {
        Console.WriteLine("Changed to " + name);
    }
}

Привязка имениName binding

Хотя каждая часть расширяемого типа должна быть объявлена в одном пространстве имен, части обычно записываются в разные объявления пространств имен.Although each part of an extensible type must be declared within the same namespace, the parts are typically written within different namespace declarations. Поэтому для каждой части могут присутствовать разные директивы using (директивы using).Thus, different using directives (Using directives) may be present for each part. При интерпретации простых имен (выводов типов) в пределах одной части учитываются только директивы using объявлений пространств имен, охватывающие эту часть.When interpreting simple names (Type inference) within one part, only the using directives of the namespace declaration(s) enclosing that part are considered. Это может привести к тому, что один и тот же идентификатор будет иметь разные значения в разных частях:This may result in the same identifier having different meanings in different parts:

namespace N
{
    using List = System.Collections.ArrayList;

    partial class A
    {
        List x;                // x has type System.Collections.ArrayList
    }
}

namespace N
{
    using List = Widgets.LinkedList;

    partial class A
    {
        List y;                // y has type Widgets.LinkedList
    }
}

Члены классаClass members

Члены класса состоят из членов, представленных в его class_member_declarations, и членов, унаследованных от прямого базового класса.The members of a class consist of the members introduced by its class_member_declarations and the members inherited from the direct base class.

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

Элементы типа класса делятся на следующие категории:The members of a class type are divided into the following categories:

  • Константы, представляющие постоянные значения, связанные с классом (константы).Constants, which represent constant values associated with the class (Constants).
  • Поля, которые являются переменными класса (поля).Fields, which are the variables of the class (Fields).
  • Методы, реализующие вычисления и действия, которые могут быть выполнены классом (методами).Methods, which implement the computations and actions that can be performed by the class (Methods).
  • Свойства, определяющие именованные характеристики и действия, связанные с чтением и записью этих характеристик (Свойства).Properties, which define named characteristics and the actions associated with reading and writing those characteristics (Properties).
  • События, определяющие уведомления, которые могут создаваться классом (событиями).Events, which define notifications that can be generated by the class (Events).
  • Индексаторы, которые позволяют индексировать экземпляры класса таким же образом (синтаксически) как массивы (индексаторы).Indexers, which permit instances of the class to be indexed in the same way (syntactically) as arrays (Indexers).
  • Операторы, которые определяют операторы выражений, которые могут применяться к экземплярам класса (операторам).Operators, which define the expression operators that can be applied to instances of the class (Operators).
  • Конструкторы экземпляров, реализующие действия, необходимые для инициализации экземпляров класса (конструкторы экземпляров)Instance constructors, which implement the actions required to initialize instances of the class (Instance constructors)
  • Деструкторы, реализующие действия, выполняемые до окончательного удаления экземпляров класса (деструкторов).Destructors, which implement the actions to be performed before instances of the class are permanently discarded (Destructors).
  • Статические конструкторы, которые реализуют действия, необходимые для инициализации самого класса (Статические конструкторы).Static constructors, which implement the actions required to initialize the class itself (Static constructors).
  • Типы, которые представляют типы, локальные для класса (вложенные типы).Types, which represent the types that are local to the class (Nested types).

Члены, которые могут содержать исполняемый код, вместе называются функциями , которые являются членами типа класса.Members that can contain executable code are collectively known as the function members of the class type. Функции-члены типа класса — это методы, свойства, события, индексаторы, операторы, конструкторы экземпляров, деструкторы и статические конструкторы этого типа класса.The function members of a class type are the methods, properties, events, indexers, operators, instance constructors, destructors, and static constructors of that class type.

Class_declaration создает новое пространство объявления (объявления) и class_member_declarations, непосредственно содержащийся в class_declaration вводит новые элементы в эту область объявления.A class_declaration creates a new declaration space (Declarations), and the class_member_declarations immediately contained by the class_declaration introduce new members into this declaration space. К class_member_declarations применяются следующие правила.The following rules apply to class_member_declarations:

  • Конструкторы экземпляров, деструкторы и статические конструкторы должны иметь то же имя, что и непосредственно включающий класс.Instance constructors, destructors and static constructors must have the same name as the immediately enclosing class. Все остальные члены должны иметь имена, которые отличаются от имен непосредственно включающего класса.All other members must have names that differ from the name of the immediately enclosing class.
  • Имя константы, поля, свойства, события или типа должно отличаться от имен всех других членов, объявленных в том же классе.The name of a constant, field, property, event, or type must differ from the names of all other members declared in the same class.
  • Имя метода должно отличаться от имен всех остальных не являющихся методами, объявленными в том же классе.The name of a method must differ from the names of all other non-methods declared in the same class. Кроме того, сигнатура (подписи и перегрузка) метода должна отличаться от сигнатур всех других методов, объявленных в том же классе, и два метода, объявленных в одном классе, могут не иметь сигнатуры, отличающиеся только ref и out.In addition, the signature (Signatures and overloading) of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref and out.
  • Сигнатура конструктора экземпляра должна отличаться от сигнатур всех других конструкторов экземпляров, объявленных в том же классе, и два конструктора, объявленные в одном классе, могут не иметь сигнатуры, отличающиеся только ref и out.The signature of an instance constructor must differ from the signatures of all other instance constructors declared in the same class, and two constructors declared in the same class may not have signatures that differ solely by ref and out.
  • Сигнатура индексатора должна отличаться от сигнатур всех других индексаторов, объявленных в том же классе.The signature of an indexer must differ from the signatures of all other indexers declared in the same class.
  • Сигнатура оператора должна отличаться от сигнатур всех других операторов, объявленных в том же классе.The signature of an operator must differ from the signatures of all other operators declared in the same class.

Унаследованные члены типа класса (наследование) не являются частью области объявления класса.The inherited members of a class type (Inheritance) are not part of the declaration space of a class. Таким образом, производный класс может объявить член с тем же именем или сигнатурой, что и у унаследованного члена (который в действительности скрывает наследуемый член).Thus, a derived class is allowed to declare a member with the same name or signature as an inherited member (which in effect hides the inherited member).

Тип экземпляраThe instance type

Каждое объявление класса имеет связанный связанный тип (привязанные и несвязанные типы), тип экземпляра.Each class declaration has an associated bound type (Bound and unbound types), the instance type. Для объявления универсального класса тип экземпляра формируется путем создания сконструированного типа (сконструированных типов) из объявления типа, где каждый из предоставленных аргументов типа является соответствующим параметром типа.For a generic class declaration, the instance type is formed by creating a constructed type (Constructed types) from the type declaration, with each of the supplied type arguments being the corresponding type parameter. Так как тип экземпляра использует параметры типа, он может использоваться только в том случае, если параметры типа находятся в области видимости; то есть внутри объявления класса.Since the instance type uses the type parameters, it can only be used where the type parameters are in scope; that is, inside the class declaration. Тип экземпляра — это тип this для кода, написанного внутри объявления класса.The instance type is the type of this for code written inside the class declaration. Для неуниверсальных классов тип экземпляра — это просто объявленный класс.For non-generic classes, the instance type is simply the declared class. Ниже показано несколько объявлений классов, а также их типы экземпляров.The following shows several class declarations along with their instance types:

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

Члены сконструированных типовMembers of constructed types

Неунаследованные члены сконструированного типа получаются подстановкой для каждого type_parameter в объявлении члена, соответствующего type_argument сконструированного типа.The non-inherited members of a constructed type are obtained by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the constructed type. Процесс подстановки основан на семантическом значении объявлений типов, а не просто в текстовой подстановке.The substitution process is based on the semantic meaning of type declarations, and is not simply textual substitution.

Например, при объявлении универсального классаFor example, given the generic class declaration

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) {...}
}

сконструированный тип Gen<int[],IComparable<string>> имеет следующие члены:the constructed type Gen<int[],IComparable<string>> has the following members:

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) {...}

Тип члена, a в объявлении универсального класса Gen является "двумерным массивом T", поэтому тип элемента a в сконструированном типе выше — "двухмерный массив из int" или int[,][].The type of the member a in the generic class declaration Gen is "two-dimensional array of T", so the type of the member a in the constructed type above is "two-dimensional array of one-dimensional array of int", or int[,][].

В членах функций экземпляров тип this — это тип экземпляра (тип экземпляра) содержащего объявления.Within instance function members, the type of this is the instance type (The instance type) of the containing declaration.

Все члены универсального класса могут использовать параметры типа из любого включающего класса либо непосредственно, либо как часть сконструированного типа.All members of a generic class can use type parameters from any enclosing class, either directly or as part of a constructed type. При использовании определенного закрытого сконструированного типа (открытые и закрытые типы) во время выполнения каждое использование параметра типа заменяется фактическим аргументом типа, переданным в сконструированный тип.When a particular closed constructed type (Open and closed types) is used at run-time, each use of a type parameter is replaced with the actual type argument supplied to the constructed type. Например:For example:

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

    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
    }
}

НаследованиеInheritance

Класс наследует члены своего прямого базового класса.A class inherits the members of its direct base class type. Наследование означает, что класс неявно содержит все члены его прямого базового класса, за исключением конструкторов экземпляров, деструкторов и статических конструкторов базового класса.Inheritance means that a class implicitly contains all members of its direct base class type, except for the instance constructors, destructors and static constructors of the base class. Ниже приведены некоторые важные аспекты наследования.Some important aspects of inheritance are:

  • Наследование является транзитивным.Inheritance is transitive. Если C является производным от B, а B является производным от A, C наследует члены, объявленные в B, а также члены, объявленные в A.If C is derived from B, and B is derived from A, then C inherits the members declared in B as well as the members declared in A.
  • Производный класс расширяет свой прямой базовый класс.A derived class extends its direct base class. Производный класс может дополнить наследуемые элементы новыми элементами, но он не может удалить определение для наследуемого члена.A derived class can add new members to those it inherits, but it cannot remove the definition of an inherited member.
  • Конструкторы экземпляров, деструкторы и статические конструкторы не наследуются, но все остальные члены не зависят от их объявленного доступа (доступ к членам).Instance constructors, destructors, and static constructors are not inherited, but all other members are, regardless of their declared accessibility (Member access). Однако в зависимости от их объявленной доступности унаследованные члены могут быть недоступны в производном классе.However, depending on their declared accessibility, inherited members might not be accessible in a derived class.
  • Производный класс может скрывать (скрывая с помощью наследования) унаследованные члены путем объявления новых членов с тем же именем или сигнатурой.A derived class can hide (Hiding through inheritance) inherited members by declaring new members with the same name or signature. Обратите внимание, что скрытие унаследованного члена не приводит к удалению этого члена — он просто делает этот член недоступным напрямую через производный класс.Note however that hiding an inherited member does not remove that member—it merely makes that member inaccessible directly through the derived class.
  • Экземпляр класса содержит набор всех полей экземпляров, объявленных в классе и его базовых классах, и неявное преобразование (неявные преобразования ссылок) из типа производного класса в любой из типов базовых классов.An instance of a class contains a set of all instance fields declared in the class and its base classes, and an implicit conversion (Implicit reference conversions) exists from a derived class type to any of its base class types. Таким образом, ссылка на экземпляр какого-либо производного класса может рассматриваться как ссылка на экземпляр любого из его базовых классов.Thus, a reference to an instance of some derived class can be treated as a reference to an instance of any of its base classes.
  • Класс может объявлять виртуальные методы, свойства и индексаторы, а производные классы могут переопределять реализацию этих членов функций.A class can declare virtual methods, properties, and indexers, and derived classes can override the implementation of these function members. Это позволяет классам работать с полиморфизмом, когда действия, выполняемые при вызове члена функции, различаются в зависимости от типа экземпляра, через который вызывается функция-член.This enables classes to exhibit polymorphic behavior wherein the actions performed by a function member invocation varies depending on the run-time type of the instance through which that function member is invoked.

Наследуемый член сконструированного типа класса — это члены непосредственных типов базового класса (базовых классов), которые обнаруживаются путем замены аргументов сконструированного типа для каждого вхождения соответствующих параметров типа в спецификации class_base .The inherited member of a constructed class type are the members of the immediate base class type (Base classes), which is found by substituting the type arguments of the constructed type for each occurrence of the corresponding type parameters in the class_base specification. Эти члены, в свою очередь, преобразуются путем подстановки для каждого type_parameter в объявлении члена, соответствующего type_argument спецификации class_base .These members, in turn, are transformed by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the class_base specification.

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

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

В приведенном выше примере сконструированный тип D<int> имеет неунаследованный член public int G(string s) получен путем замены аргумента типа int для параметра типа T.In the above example, the constructed type D<int> has a non-inherited member public int G(string s) obtained by substituting the type argument int for the type parameter T. D<int> также имеет унаследованный член из объявления класса B.D<int> also has an inherited member from the class declaration B. Этот наследуемый член определяется первым определением типа базового класса B<int[]> D<int> путем замены int для T в спецификации базового класса B<T[]>.This inherited member is determined by first determining the base class type B<int[]> of D<int> by substituting int for T in the base class specification B<T[]>. Затем, в качестве аргумента типа для B, int[] подставляется для U в public U F(long index), возвращая унаследованный член public int[] F(long index).Then, as a type argument to B, int[] is substituted for U in public U F(long index), yielding the inherited member public int[] F(long index).

Модификатор newThe new modifier

Class_member_declaration разрешено объявлять член с тем же именем или сигнатурой, что и у унаследованного члена.A class_member_declaration is permitted to declare a member with the same name or signature as an inherited member. В этом случае член производного класса называется скрытием члена базового класса.When this occurs, the derived class member is said to hide the base class member. Скрытие наследуемого члена не считается ошибкой, но оно приводит к выдаче предупреждения компилятором.Hiding an inherited member is not considered an error, but it does cause the compiler to issue a warning. Чтобы отключить это предупреждение, объявление члена производного класса может включать модификатор new, указывающий, что производный член предназначен для скрытия базового члена.To suppress the warning, the declaration of the derived class member can include a new modifier to indicate that the derived member is intended to hide the base member. Этот раздел обсуждается далее в разделе Скрытие с помощью наследования.This topic is discussed further in Hiding through inheritance.

Если модификатор new включен в объявление, которое не скрывает унаследованный член, выдается предупреждение об этом.If a new modifier is included in a declaration that doesn't hide an inherited member, a warning to that effect is issued. Это предупреждение подавляется путем удаления модификатора new.This warning is suppressed by removing the new modifier.

Модификаторы доступаAccess modifiers

Class_member_declaration может иметь любой из пяти возможных разновидностей объявленного уровня доступности (объявленный уровень доступности): public, protected internal, protected, internalили private.A class_member_declaration can have any one of the five possible kinds of declared accessibility (Declared accessibility): public, protected internal, protected, internal, or private. За исключением сочетания protected internal, при указании более одного модификатора доступа возникает ошибка времени компиляции.Except for the protected internal combination, it is a compile-time error to specify more than one access modifier. Если class_member_declaration не содержит модификаторы доступа, предполагается private.When a class_member_declaration does not include any access modifiers, private is assumed.

Составные типыConstituent types

Типы, используемые в объявлении члена, называются составляющими типами этого члена.Types that are used in the declaration of a member are called the constituent types of that member. Возможные составные типы — это тип константы, поля, свойства, события или индексатора, тип возвращаемого значения метода или оператора, а также типы параметров метода, индексатора, оператора или конструктора экземпляра.Possible constituent types are the type of a constant, field, property, event, or indexer, the return type of a method or operator, and the parameter types of a method, indexer, operator, or instance constructor. Составные типы элемента должны иметь по крайней мере такой же уровень доступности, как и сам элемент (ограничения доступности).The constituent types of a member must be at least as accessible as that member itself (Accessibility constraints).

Статические и члены экземпляровStatic and instance members

Члены класса являются статическими элементами или членами экземпляра.Members of a class are either static members or instance members. В целом, статические члены можно рассматривать как принадлежащие типам классов и членам экземпляров как принадлежащие объектам (экземплярам типов классов).Generally speaking, it is useful to think of static members as belonging to class types and instance members as belonging to objects (instances of class types).

Если объявление поля, метода, свойства, события, оператора или конструктора содержит модификатор static, он объявляет статический элемент.When a field, method, property, event, operator, or constructor declaration includes a static modifier, it declares a static member. Кроме того, константа или объявление типа неявно объявляет статический член.In addition, a constant or type declaration implicitly declares a static member. Статические члены имеют следующие характеристики.Static members have the following characteristics:

  • При ссылке на статический элемент M в member_access (доступ к члену) формы E.M, E должен обменяться типом, содержащим M.When a static member M is referenced in a member_access (Member access) of the form E.M, E must denote a type containing M. Ошибка времени компиляции E для обозначения экземпляра.It is a compile-time error for E to denote an instance.
  • Статическое поле определяет ровно одно место хранения, которое должно совместно использоваться всеми экземплярами данного закрытого типа класса.A static field identifies exactly one storage location to be shared by all instances of a given closed class type. Независимо от того, сколько экземпляров данного закрытого типа класса создается, существует только одна копия статического поля.No matter how many instances of a given closed class type are created, there is only ever one copy of a static field.
  • Статический член функции (метод, свойство, событие, оператор или конструктор) не работает с конкретным экземпляром, и это ошибка времени компиляции для ссылки на this в таком члене функции.A static function member (method, property, event, operator, or constructor) does not operate on a specific instance, and it is a compile-time error to refer to this in such a function member.

Если объявление поля, метода, свойства, события, индексатора, конструктора или деструктора не включает модификатор static, он объявляет член экземпляра.When a field, method, property, event, indexer, constructor, or destructor declaration does not include a static modifier, it declares an instance member. (Член экземпляра иногда называется не статическим членом.) Члены экземпляра имеют следующие характеристики.(An instance member is sometimes called a non-static member.) Instance members have the following characteristics:

  • Если в member_access (доступ к члену) E.Mформы M ссылка на член экземпляра, E должен обся экземпляр типа, содержащего M.When an instance member M is referenced in a member_access (Member access) of the form E.M, E must denote an instance of a type containing M. Это ошибка времени привязки для E обозначения типа.It is a binding-time error for E to denote a type.
  • Каждый экземпляр класса содержит отдельный набор всех полей экземпляров класса.Every instance of a class contains a separate set of all instance fields of the class.
  • Член функции экземпляра (метод, свойство, индексатор, конструктор экземпляра или деструктор) работает с данным экземпляром класса, и доступ к этому экземпляру можно получить как this (этот доступ).An instance function member (method, property, indexer, instance constructor, or destructor) operates on a given instance of the class, and this instance can be accessed as this (This access).

В следующем примере показаны правила доступа к статическим и членам экземпляров.The following example illustrates the rules for accessing static and instance members:

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
    }
}

Метод F показывает, что в члене функции экземпляра simple_name (простые имена) можно использовать для доступа к членам экземпляра и статическим элементам.The F method shows that in an instance function member, a simple_name (Simple names) can be used to access both instance members and static members. Метод G показывает, что в статическом члене функции это ошибка времени компиляции для доступа к члену экземпляра через simple_name.The G method shows that in a static function member, it is a compile-time error to access an instance member through a simple_name. Метод Main показывает, что в member_access (доступ к членам) доступ к членам экземпляра должен осуществляться через экземпляры, а доступ к статическим членам должен осуществляться через типы.The Main method shows that in a member_access (Member access), instance members must be accessed through instances, and static members must be accessed through types.

Вложенные типыNested types

Тип, объявленный в объявлении класса или структуры, называется вложенным типом.A type declared within a class or struct declaration is called a nested type. Тип, объявленный в пределах единицы компиляции или пространства имен, называется невложенным типом.A type that is declared within a compilation unit or namespace is called a non-nested type.

в примереIn the example

using System;

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

класс B является вложенным типом, так как он объявлен в классе A, а класс A является невложенным типом, так как он объявлен в блоке компиляции.class B is a nested type because it is declared within class A, and class A is a non-nested type because it is declared within a compilation unit.

Полное имяFully qualified name

Полное имя (полныеимена) для вложенного типа S.N, где S — полное имя типа, в котором объявлен тип N.The fully qualified name (Fully qualified names) for a nested type is S.N where S is the fully qualified name of the type in which type N is declared.

Объявленная доступностьDeclared accessibility

Невложенные типы могут иметь public или internal объявленный уровень доступности и иметь internal объявленный уровень доступности по умолчанию.Non-nested types can have public or internal declared accessibility and have internal declared accessibility by default. Вложенные типы могут также иметь эти формы объявленного уровня доступности и одну или несколько дополнительных форм объявленной доступности в зависимости от того, является ли вмещающий тип классом или структурой:Nested types can have these forms of declared accessibility too, plus one or more additional forms of declared accessibility, depending on whether the containing type is a class or struct:

  • Вложенный тип, объявленный в классе, может иметь любую из пяти форм объявленной доступности (public, protected internal, protected, internalили private) и, как и другие члены класса, по умолчанию private объявленной доступности.A nested type that is declared in a class can have any of five forms of declared accessibility (public, protected internal, protected, internal, or private) and, like other class members, defaults to private declared accessibility.
  • Вложенный тип, объявленный в структуре, может иметь любую из трех форм объявленной доступности (public, internalили private) и, как и другие члены структуры, по умолчанию private объявленной доступности.A nested type that is declared in a struct can have any of three forms of declared accessibility (public, internal, or private) and, like other struct members, defaults to private declared accessibility.

ПримерThe example

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 {...} }
}

объявляет закрытый вложенный класс Node.declares a private nested class Node.

ОтображениеHiding

Вложенный тип может скрываться (Скрытие) базового члена.A nested type may hide (Name hiding) a base member. Модификатор new разрешен в объявлениях вложенного типа, поэтому скрытие можно выразить явным образом.The new modifier is permitted on nested type declarations so that hiding can be expressed explicitly. ПримерThe example

using System;

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

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

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

показывает вложенный класс M, который скрывает метод M, определенный в Base.shows a nested class M that hides the method M defined in Base.

Этот доступthis access

Вложенный тип и содержащий его тип не имеют особой связи с this_access (этот доступ).A nested type and its containing type do not have a special relationship with regard to this_access (This access). В частности, this внутри вложенного типа нельзя использовать для ссылки на элементы экземпляра содержащего типа.Specifically, this within a nested type cannot be used to refer to instance members of the containing type. В случаях, когда вложенному типу требуется доступ к членам экземпляра содержащего его типа, доступ можно предоставить, предоставив this для экземпляра содержащего типа в качестве аргумента конструктора для вложенного типа.In cases where a nested type needs access to the instance members of its containing type, access can be provided by providing the this for the instance of the containing type as a constructor argument for the nested type. В следующем примереThe following example

using System;

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();
    }
}

показывает этот метод.shows this technique. Экземпляр C создает экземпляр Nested и передает его собственный this конструктору Nested, чтобы обеспечить последующий доступ к членам экземпляра C.An instance of C creates an instance of Nested and passes its own this to Nested's constructor in order to provide subsequent access to C's instance members.

Доступ к закрытым и защищенным элементам содержащего типаAccess to private and protected members of the containing type

Вложенный тип имеет доступ ко всем членам, доступным для содержащего его типа, включая элементы содержащего типа, которые имеют private и protected объявленный уровень доступа.A nested type has access to all of the members that are accessible to its containing type, including members of the containing type that have private and protected declared accessibility. ПримерThe example

using System;

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();
    }
}

показывает класс C, содержащий вложенный класс Nested.shows a class C that contains a nested class Nested. В Nestedметод G вызывает статический метод F, определенный в C, а F имеет закрытую объявленную доступность.Within Nested, the method G calls the static method F defined in C, and F has private declared accessibility.

Вложенный тип также может обращаться к защищенным членам, определенным в базовом типе содержащего его типа.A nested type also may access protected members defined in a base type of its containing type. в примереIn the example

using System;

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();
    }
}

вложенный класс Derived.Nested обращается к защищенному методу F, определенному в базовом классе Derived, Base, путем вызова через экземпляр Derived.the nested class Derived.Nested accesses the protected method F defined in Derived's base class, Base, by calling through an instance of Derived.

Вложенные типы в универсальных классахNested types in generic classes

Объявление универсального класса может содержать объявления вложенных типов.A generic class declaration can contain nested type declarations. Параметры типа включающего класса могут использоваться внутри вложенных типов.The type parameters of the enclosing class can be used within the nested types. Объявление вложенного типа может содержать дополнительные параметры типа, которые применяются только к вложенному типу.A nested type declaration can contain additional type parameters that apply only to the nested type.

Каждое объявление типа, содержащееся в объявлении универсального класса, неявно является объявлением универсального типа.Every type declaration contained within a generic class declaration is implicitly a generic type declaration. При записи ссылки на тип, вложенный в универсальный тип, вмещающий сконструированный тип, включая его аргументы типа, должен иметь имя.When writing a reference to a type nested within a generic type, the containing constructed type, including its type arguments, must be named. Однако внутри внешнего класса вложенный тип можно использовать без уточнения. Тип экземпляра внешнего класса может быть неявно использован при создании вложенного типа.However, from within the outer class, the nested type can be used without qualification; the instance type of the outer class can be implicitly used when constructing the nested type. В следующем примере показаны три различных способа ссылки на сконструированный тип, созданный из Inner; Первые два эквивалентны:The following example shows three different correct ways to refer to a constructed type created from Inner; the first two are equivalent:

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
    }
}

Несмотря на неправильный стиль программирования, параметр типа во вложенном типе может скрывать член или параметр типа, объявленный во внешнем типе:Although it is bad programming style, a type parameter in a nested type can hide a member or type parameter declared in the outer type:

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

Зарезервированные имена элементовReserved member names

Чтобы упростить базовую C# реализацию времени выполнения, для каждого объявления исходного члена, которое является свойством, событием или индексатором, реализация должна резервировать две сигнатуры метода на основе типа объявления члена, его имени и типа.To facilitate the underlying C# run-time implementation, for each source member declaration that is a property, event, or indexer, the implementation must reserve two method signatures based on the kind of the member declaration, its name, and its type. Ошибка времени компиляции возникает, когда программа объявляет член, сигнатура которого совпадает с одной из зарезервированных сигнатур, даже если базовая реализация времени выполнения не использует эти резервирования.It is a compile-time error for a program to declare a member whose signature matches one of these reserved signatures, even if the underlying run-time implementation does not make use of these reservations.

Зарезервированные имена не представляют собой объявления, поэтому они не участвуют в поиске членов.The reserved names do not introduce declarations, thus they do not participate in member lookup. Однако связанные с объявлением сигнатуры методов участвуют в наследовании (наследовании) и могут быть скрыты с помощью модификатора new (Модификатор new).However, a declaration's associated reserved method signatures do participate in inheritance (Inheritance), and can be hidden with the new modifier (The new modifier).

Резервирование этих имен служит трем целям:The reservation of these names serves three purposes:

  • Значение, чтобы разрешить базовой реализации использовать обычный идентификатор в качестве имени метода для получения или установки доступа к C# языковой функции.To allow the underlying implementation to use an ordinary identifier as a method name for get or set access to the C# language feature.
  • Значение, чтобы разрешить другим языкам взаимодействовать с помощью обычного идентификатора в качестве имени метода для получения или установки доступа C# к языковой функции.To allow other languages to interoperate using an ordinary identifier as a method name for get or set access to the C# language feature.
  • Чтобы убедиться в том, что источник, принимаемый одним согласованным компилятором, принимается другим, заставляя конкретные имена зарезервированных членов во всех C# реализациях.To help ensure that the source accepted by one conforming compiler is accepted by another, by making the specifics of reserved member names consistent across all C# implementations.

Объявление деструктора (дедеструкторов) также приводит к зарезервированию сигнатуры (имена членов зарезервированы для деструкторов).The declaration of a destructor (Destructors) also causes a signature to be reserved (Member names reserved for destructors).

Имена членов, зарезервированных для свойствMember names reserved for properties

Для свойства P (Свойства) типа Tследующие сигнатуры зарезервированы:For a property P (Properties) of type T, the following signatures are reserved:

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

Обе сигнатуры зарезервированы, даже если свойство доступно только для чтения или только для записи.Both signatures are reserved, even if the property is read-only or write-only.

в примереIn the example

using System;

class A
{
    public int P {
        get { return 123; }
    }
}

class B: A
{
    new public int get_P() {
        return 456;
    }

    new public 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());
    }
}

класс A определяет свойство, доступное только для чтения P, тем самым сохраняя сигнатуры для методов get_P и set_P.a class A defines a read-only property P, thus reserving signatures for get_P and set_P methods. Класс B является производным от A и скрывает обе эти зарезервированные сигнатуры.A class B derives from A and hides both of these reserved signatures. В примере выводится результат:The example produces the output:

123
123
456

Имена членов, зарезервированных для событийMember names reserved for events

Для событий E (событий) типа делегата Tзарезервированы следующие сигнатуры:For an event E (Events) of delegate type T, the following signatures are reserved:

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

Имена членов, зарезервированных для индексаторовMember names reserved for indexers

Для индексатора (индексаторов) типа T с Lом parameter-list зарезервированы следующие сигнатуры:For an indexer (Indexers) of type T with parameter-list L, the following signatures are reserved:

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

Обе сигнатуры зарезервированы, даже если индексатор доступен только для чтения или только для записи.Both signatures are reserved, even if the indexer is read-only or write-only.

Более того, имя члена Item зарезервировано.Furthermore the member name Item is reserved.

Имена членов, зарезервированных для деструкторовMember names reserved for destructors

Для класса, содержащего деструктор (дедеструкторы), зарезервирована следующая сигнатура:For a class containing a destructor (Destructors), the following signature is reserved:

void Finalize();

КонстантыConstants

Константа — это член класса, представляющий постоянное значение: значение, которое может быть вычислено во время компиляции.A constant is a class member that represents a constant value: a value that can be computed at compile-time. Constant_declaration вводит одну или несколько констант заданного типа.A constant_declaration introduces one or more constants of a given type.

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

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

constant_declarators
    : constant_declarator (',' constant_declarator)*
    ;

constant_declarator
    : identifier '=' constant_expression
    ;

Constant_declaration может включать набор атрибутов (атрибутов), модификатор new (Модификатор new) и допустимое сочетание четырех модификаторов доступа (модификаторы доступа).A constant_declaration may include a set of attributes (Attributes), a new modifier (The new modifier), and a valid combination of the four access modifiers (Access modifiers). Атрибуты и модификаторы применяются ко всем членам, объявленным constant_declaration.The attributes and modifiers apply to all of the members declared by the constant_declaration. Несмотря на то, что константы считаются статическими членами, constant_declaration не требуется и не допускает модификатор static.Even though constants are considered static members, a constant_declaration neither requires nor allows a static modifier. Ошибка одного и того же модификатора встречается несколько раз в объявлении константы.It is an error for the same modifier to appear multiple times in a constant declaration.

Тип constant_declaration указывает тип элементов, введенных объявлением.The type of a constant_declaration specifies the type of the members introduced by the declaration. За типом следует список constant_declarators, каждый из которых представляет новый элемент.The type is followed by a list of constant_declarators, each of which introduces a new member. Constant_declarator состоит из идентификатора , который именует член, за которым следует маркер "=", за которым следует constant_expression (константные выражения), которая предоставляет значение элемента.A constant_declarator consists of an identifier that names the member, followed by an "=" token, followed by a constant_expression (Constant expressions) that gives the value of the member.

Тип , указанный в объявлении константы, должен быть sbyte, byte, short, ushort, int, uint, long, ulong, char, float , doubleили decimal .bool``stringThe type specified in a constant declaration must be sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum_type, or a reference_type. Каждый constant_expression должен возвращать значение целевого типа или типа, который можно преобразовать в целевой тип с помощью неявного преобразования (неявные преобразования).Each constant_expression must yield a value of the target type or of a type that can be converted to the target type by an implicit conversion (Implicit conversions).

Тип константы должен иметь по крайней мере такой же уровень доступности, как и сама константа (ограничения доступности).The type of a constant must be at least as accessible as the constant itself (Accessibility constraints).

Значение константы получается в выражении с помощью simple_name (простые имена) или member_access (доступ к члену).The value of a constant is obtained in an expression using a simple_name (Simple names) or a member_access (Member access).

Константа сама по себе может участвовать в constant_expression.A constant can itself participate in a constant_expression. Поэтому константа может использоваться в любой конструкции, для которой требуется constant_expression.Thus, a constant may be used in any construct that requires a constant_expression. Примеры таких конструкций включают case метки, goto case операторы, enum объявления элементов, атрибуты и другие объявления констант.Examples of such constructs include case labels, goto case statements, enum member declarations, attributes, and other constant declarations.

Как описано в разделе константные выражения, constant_expression — это выражение, которое может быть полностью вычислено во время компиляции.As described in Constant expressions, a constant_expression is an expression that can be fully evaluated at compile-time. Поскольку единственным способом создания ненулевого значения reference_type , отличного от string, является применение оператора new, а поскольку оператор new не разрешен в constant_expression, единственным возможным значением для констант reference_types, кроме string, является null.Since the only way to create a non-null value of a reference_type other than string is to apply the new operator, and since the new operator is not permitted in a constant_expression, the only possible value for constants of reference_types other than string is null.

Если требуется символьное имя для постоянного значения, но если тип этого значения не разрешен в объявлении константы или если значение не может быть вычислено во время компиляции с помощью constant_expression, вместо этого можно использовать поле readonly (поля только для чтения).When a symbolic name for a constant value is desired, but when the type of that value is not permitted in a constant declaration, or when the value cannot be computed at compile-time by a constant_expression, a readonly field (Readonly fields) may be used instead.

Объявление константы, которое объявляет несколько констант, эквивалентно нескольким объявлениям одиночных констант с одинаковыми атрибутами, модификаторами и типом.A constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same attributes, modifiers, and type. ПримерFor example

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

эквивалентноis equivalent to

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

Константы могут зависеть от других констант в той же программе, если зависимости не являются циклической природой.Constants are permitted to depend on other constants within the same program as long as the dependencies are not of a circular nature. Компилятор автоматически упорядочивает вычисление объявлений констант в соответствующем порядке.The compiler automatically arranges to evaluate the constant declarations in the appropriate order. в примереIn the example

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

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

компилятор сначала вычисляет A.Y, затем вычисляет B.Zи, наконец, вычисляет A.X, создавая значения 10, 11и 12.the compiler first evaluates A.Y, then evaluates B.Z, and finally evaluates A.X, producing the values 10, 11, and 12. Объявления констант могут зависеть от констант других программ, но такие зависимости возможны только в одном направлении.Constant declarations may depend on constants from other programs, but such dependencies are only possible in one direction. Ссылаясь на приведенный выше пример, если A и B были объявлены в отдельных программах, A.X будет зависеть от B.Z, но B.Z может не зависеть одновременно от A.Y.Referring to the example above, if A and B were declared in separate programs, it would be possible for A.X to depend on B.Z, but B.Z could then not simultaneously depend on A.Y.

ПоляFields

Поле — это элемент, представляющий переменную, связанную с объектом или классом.A field is a member that represents a variable associated with an object or class. Field_declaration вводит одно или несколько полей заданного типа.A field_declaration introduces one or more fields of a given type.

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

field_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'readonly'
    | 'volatile'
    | field_modifier_unsafe
    ;

variable_declarators
    : variable_declarator (',' variable_declarator)*
    ;

variable_declarator
    : identifier ('=' variable_initializer)?
    ;

variable_initializer
    : expression
    | array_initializer
    ;

Field_declaration может включать набор атрибутов (атрибутов), модификатор new (Модификатор new), допустимое сочетание четырех модификаторов доступа (модификаторов доступа) и модификатора static (статические и поля экземпляров).A field_declaration may include a set of attributes (Attributes), a new modifier (The new modifier), a valid combination of the four access modifiers (Access modifiers), and a static modifier (Static and instance fields). Кроме того, field_declaration может включать модификатор readonly (поля только для чтения) или модификатор volatile (изменяемые поля), но не оба значения одновременно.In addition, a field_declaration may include a readonly modifier (Readonly fields) or a volatile modifier (Volatile fields) but not both. Атрибуты и модификаторы применяются ко всем членам, объявленным field_declaration.The attributes and modifiers apply to all of the members declared by the field_declaration. Ошибка одного и того же модификатора встречается несколько раз в объявлении поля.It is an error for the same modifier to appear multiple times in a field declaration.

Тип field_declaration указывает тип элементов, введенных объявлением.The type of a field_declaration specifies the type of the members introduced by the declaration. За типом следует список variable_declarators, каждый из которых представляет новый элемент.The type is followed by a list of variable_declarators, each of which introduces a new member. Variable_declarator состоит из идентификатора , который присваивает элементу имя, при необходимости за которым следует маркер "=" и variable_initializer (Инициализаторы переменных), которые предоставляют начальное значение этого элемента.A variable_declarator consists of an identifier that names that member, optionally followed by an "=" token and a variable_initializer (Variable initializers) that gives the initial value of that member.

Тип поля должен иметь по крайней мере такой же уровень доступности, как и само поле (ограничения доступности).The type of a field must be at least as accessible as the field itself (Accessibility constraints).

Значение поля получается в выражении с помощью simple_name (простые имена) или member_access (доступ к члену).The value of a field is obtained in an expression using a simple_name (Simple names) or a member_access (Member access). Значение поля, не допускающего чтение, изменяется с помощью присваивания (Операторы присваивания).The value of a non-readonly field is modified using an assignment (Assignment operators). Значение поля, не допускающее доступ только для чтения, может быть получено и изменено с помощью постфиксных операторов инкремента и декремента (Постфиксные операторы инкремента и декремента) и операторов инкремента и декремента (префиксные операторы увеличения и уменьшения).The value of a non-readonly field can be both obtained and modified using postfix increment and decrement operators (Postfix increment and decrement operators) and prefix increment and decrement operators (Prefix increment and decrement operators).

Объявление поля, которое объявляет несколько полей, эквивалентно нескольким объявлениям отдельных полей с одинаковыми атрибутами, модификаторами и типом.A field declaration that declares multiple fields is equivalent to multiple declarations of single fields with the same attributes, modifiers, and type. ПримерFor example

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

эквивалентноis equivalent to

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

Статические и поля экземпляровStatic and instance fields

Если объявление поля включает модификатор static, поля, представленные в объявлении, являются статическими полями.When a field declaration includes a static modifier, the fields introduced by the declaration are static fields. Если модификатор static отсутствует, поля, представленные в объявлении, являются полями экземпляра.When no static modifier is present, the fields introduced by the declaration are instance fields. Статические поля и поля экземпляров — это два типа переменных (переменных) C#, поддерживаемых, и иногда они называются статическими переменными и переменными экземплярасоответственно.Static fields and instance fields are two of the several kinds of variables (Variables) supported by C#, and at times they are referred to as static variables and instance variables, respectively.

Статическое поле не является частью конкретного экземпляра; Вместо этого он совместно используется всеми экземплярами закрытого типа (открытые и закрытые типы).A static field is not part of a specific instance; instead, it is shared amongst all instances of a closed type (Open and closed types). Независимо от того, сколько экземпляров закрытого типа класса создается, существует только одна копия статического поля для соответствующего домена приложения.No matter how many instances of a closed class type are created, there is only ever one copy of a static field for the associated application domain.

Например:For example:

class C<V>
{
    static int count = 0;

    public C() {
        count++;
    }

    public static int Count {
        get { return count; }
    }
}

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

        C<double> x2 = new C<double>();
        Console.WriteLine(C<int>.Count);        // Prints 1

        C<int> x3 = new C<int>();
        Console.WriteLine(C<int>.Count);        // Prints 2
    }
}

Поле экземпляра принадлежит экземпляру.An instance field belongs to an instance. В частности, каждый экземпляр класса содержит отдельный набор всех полей экземпляров этого класса.Specifically, every instance of a class contains a separate set of all the instance fields of that class.

Если на поле имеется ссылка в member_access (доступ к элементу) в форме E.M, если M является статическим полем, E должен обменяться типом, содержащим M, а если M является полем экземпляра, E должен обменять экземпляр типа, содержащего M.When a field is referenced in a member_access (Member access) of the form E.M, if M is a static field, E must denote a type containing M, and if M is an instance field, E must denote an instance of a type containing M.

Различия между статическими и членами экземпляра обсуждаются далее в статических членах и экземплярах.The differences between static and instance members are discussed further in Static and instance members.

Поля только для чтенияReadonly fields

Если field_declaration включает модификатор readonly, поля, представленные в объявлении, являются полями только для чтения.When a field_declaration includes a readonly modifier, the fields introduced by the declaration are readonly fields. Прямые назначения для полей, доступных только для чтения, могут использоваться только как часть этого объявления или в конструкторе экземпляра или статическом конструкторе в том же классе.Direct assignments to readonly fields can only occur as part of that declaration or in an instance constructor or static constructor in the same class. (Поле ReadOnly может быть назначено несколько раз в этих контекстах.) В частности, прямые назначения readonly полю разрешены только в следующих контекстах:(A readonly field can be assigned to multiple times in these contexts.) Specifically, direct assignments to a readonly field are permitted only in the following contexts:

  • В variable_declarator , который вводит поле (путем включения variable_initializer в объявление).In the variable_declarator that introduces the field (by including a variable_initializer in the declaration).
  • Для поля экземпляра в конструкторах экземпляров класса, содержащего объявление поля; для статического поля в статическом конструкторе класса, содержащего объявление поля.For an instance field, in the instance constructors of the class that contains the field declaration; for a static field, in the static constructor of the class that contains the field declaration. Это также единственные контексты, в которых допускается передача readonly поля в качестве параметра out или ref.These are also the only contexts in which it is valid to pass a readonly field as an out or ref parameter.

Попытка присвоить readonly полю или передать его в качестве параметра out или ref в любом другом контексте — это ошибка времени компиляции.Attempting to assign to a readonly field or pass it as an out or ref parameter in any other context is a compile-time error.

Использование статических полей только для чтения для константUsing static readonly fields for constants

static readonly поле полезно, когда требуется символьное имя для постоянного значения, но если тип значения не разрешен в объявлении const или если значение не может быть вычислено во время компиляции.A static readonly field is useful when a symbolic name for a constant value is desired, but when the type of the value is not permitted in a const declaration, or when the value cannot be computed at compile-time. в примереIn the example

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;
    }
}

члены Black, White, Red, Greenи Blue не могут быть объявлены как const члены, так как их значения не могут быть вычислены во время компиляции.the Black, White, Red, Green, and Blue members cannot be declared as const members because their values cannot be computed at compile-time. Тем не менее, объявление этих static readonly вместо этого оказывает практически одинаковый результат.However, declaring them static readonly instead has much the same effect.

Управление версиями констант и статических полей только для чтенияVersioning of constants and static readonly fields

Константы и поля только для чтения имеют разную семантику двоичного управления версиями.Constants and readonly fields have different binary versioning semantics. Когда выражение ссылается на константу, значение константы получается во время компиляции, но если выражение ссылается на поле только для чтения, значение этого поля не будет получено до времени выполнения.When an expression references a constant, the value of the constant is obtained at compile-time, but when an expression references a readonly field, the value of the field is not obtained until run-time. Рассмотрим приложение, состоящее из двух отдельных программ:Consider an application that consists of two separate programs:

using System;

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

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

Пространства имен Program1 и Program2 обозначают две программы, компилируемые отдельно.The Program1 and Program2 namespaces denote two programs that are compiled separately. Поскольку Program1.Utils.X объявляется как статическое поле только для чтения, значение, выводимое инструкцией Console.WriteLine, неизвестно во время компиляции, а получается во время выполнения.Because Program1.Utils.X is declared as a static readonly field, the value output by the Console.WriteLine statement is not known at compile-time, but rather is obtained at run-time. Таким словами, если значение X изменяется и перекомпилируется Program1, инструкция Console.WriteLine выводит новое значение, даже если Program2 не перекомпилируется.Thus, if the value of X is changed and Program1 is recompiled, the Console.WriteLine statement will output the new value even if Program2 isn't recompiled. Однако X был константой, значение X было бы получено во время Program2 компиляции, и оно останется без изменений в Program1 до повторной компиляции Program2.However, had X been a constant, the value of X would have been obtained at the time Program2 was compiled, and would remain unaffected by changes in Program1 until Program2 is recompiled.

Изменяемые поляVolatile fields

Если field_declaration включает модификатор volatile, поля, представленные этим объявлением, являются временными полями.When a field_declaration includes a volatile modifier, the fields introduced by that declaration are volatile fields.

Для неизменяемых полей методы оптимизации, которые переупорядочивают инструкции, могут привести к непредвиденным и непредсказуемым результатам в многопоточных программах, обращающихся к полям без синхронизации, например, предоставленной lock_statement (инструкция Lock).For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock_statement (The lock statement). Эти оптимизации могут выполняться компилятором, системой времени выполнения или оборудованием.These optimizations can be performed by the compiler, by the run-time system, or by hardware. Для переменных полей, таких как оптимизация с изменением порядка, ограничено:For volatile fields, such reordering optimizations are restricted:

  • Чтение изменяемого поля называется временным чтением.A read of a volatile field is called a volatile read. Для постоянного чтения существует "семантика получения"; то есть перед всеми ссылками на память, которая происходит после этого в последовательности инструкций, будет гарантированно выполняться.A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
  • Запись изменяемого поля называется временной записью.A write of a volatile field is called a volatile write. Временная запись имеет значение "семантика выпуска"; то есть это гарантируется после любой ссылки на память до инструкции Write в последовательности инструкций.A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.

Эти ограничения гарантируют, что все потоки будут видеть временные записи, выполняемые другим потоком, в порядке выполнения.These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed. Реализация согласования не требуется для предоставления единого общего упорядочения временных операций записи, как показано во всех потоках выполнения.A conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution. Тип изменяемого поля должен быть одним из следующих:The type of a volatile field must be one of the following:

  • Reference_type.A reference_type.
  • Тип byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtrили System.UIntPtr.The type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr.
  • Enum_type с базовым типом перечисления byte, sbyte, short, ushort, intили uint.An enum_type having an enum base type of byte, sbyte, short, ushort, int, or uint.

ПримерThe example

using System;
using System.Threading;

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 = {0}", result);
                return;
            }
        }
    }
}

выводятся следующие выходные данные:produces the output:

result = 143

В этом примере метод Main запускает новый поток, который выполняет Thread2метода.In this example, the method Main starts a new thread that runs the method Thread2. Этот метод сохраняет значение в поле без постоянного вызова с именем result, а затем сохраняет true в поле volatile finished.This method stores a value into a non-volatile field called result, then stores true in the volatile field finished. Основной поток ожидает, когда поле finished будет установлено в true, а затем считывает resultполя.The main thread waits for the field finished to be set to true, then reads the field result. Поскольку finished был объявлен volatile, главный поток должен считать значение 143 из поля result.Since finished has been declared volatile, the main thread must read the value 143 from the field result. Если поле finished не было объявлено volatile, то было бы допустимо, чтобы хранилище было resultо видимым для основного потока после того, как хранилище finished, и поэтому главный поток считывает значение 0 из поля result.If the field finished had not been declared volatile, then it would be permissible for the store to result to be visible to the main thread after the store to finished, and hence for the main thread to read the value 0 from the field result. Объявление finished как поля volatile предотвращает такую несогласованность.Declaring finished as a volatile field prevents any such inconsistency.

Инициализация поляField initialization

Начальное значение поля, будь то статическое поле или поле экземпляра, является значением по умолчанию (значения по умолчанию) для типа поля.The initial value of a field, whether it be a static field or an instance field, is the default value (Default values) of the field's type. Невозможно наблюдать за значением поля до того, как была выполнена инициализация по умолчанию, поэтому поле не может быть неинициализированным.It is not possible to observe the value of a field before this default initialization has occurred, and a field is thus never "uninitialized". ПримерThe example

using System;

class Test
{
    static bool b;
    int i;

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

выводятся следующие выходные данныеproduces the output

b = False, i = 0

так как b и i автоматически инициализируются значениями по умолчанию.because b and i are both automatically initialized to default values.

Инициализаторы переменныхVariable initializers

Объявления полей могут включать variable_initializers.Field declarations may include variable_initializers. Для статических полей Инициализаторы переменных соответствуют операторам присваивания, выполняемым во время инициализации класса.For static fields, variable initializers correspond to assignment statements that are executed during class initialization. Для полей экземпляров Инициализаторы переменных соответствуют операторам присваивания, выполняемым при создании экземпляра класса.For instance fields, variable initializers correspond to assignment statements that are executed when an instance of the class is created.

ПримерThe example

using System;

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 = {0}, i = {1}, s = {2}", x, a.i, a.s);
    }
}

выводятся следующие выходные данныеproduces the output

x = 1.4142135623731, i = 100, s = Hello

поскольку присваивание x происходит, когда статические инициализаторы полей выполняются и назначений i и s происходят при выполнении инициализаторов полей экземпляров.because an assignment to x occurs when static field initializers execute and assignments to i and s occur when the instance field initializers execute.

Инициализация значения по умолчанию, описанная в разделе Инициализация полей , выполняется для всех полей, включая поля с инициализаторами переменных.The default value initialization described in Field initialization occurs for all fields, including fields that have variable initializers. Таким словами, при инициализации класса все статические поля в этом классе сначала инициализируются значениями по умолчанию, а затем статические инициализаторы полей выполняются в текстовом порядке.Thus, when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. Аналогично, при создании экземпляра класса все поля экземпляров в этом экземпляре сначала инициализируются значениями по умолчанию, а затем инициализаторы полей экземпляров выполняются в текстовом порядке.Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order.

Статические поля с инициализаторами переменных можно наблюдать в состоянии значения по умолчанию.It is possible for static fields with variable initializers to be observed in their default value state. Однако в этом случае настоятельно не рекомендуется делать это в зависимости от стиля.However, this is strongly discouraged as a matter of style. ПримерThe example

using System;

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

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

Это поведение характерно.exhibits this behavior. Несмотря на циклические определения a и b, программа является допустимой.Despite the circular definitions of a and b, the program is valid. В результате получается результат.It results in the output

a = 1, b = 2

так как статические поля a и b инициализируются значением 0 (значение по умолчанию для int) перед выполнением инициализаторов.because the static fields a and b are initialized to 0 (the default value for int) before their initializers are executed. При выполнении инициализатора для a значение b равно нулю, поэтому a инициализируется в 1.When the initializer for a runs, the value of b is zero, and so a is initialized to 1. При выполнении инициализатора для b значение a уже 1, поэтому b инициализируется в 2.When the initializer for b runs, the value of a is already 1, and so b is initialized to 2.

Инициализация статических полейStatic field initialization

Инициализаторы статических переменных полей класса соответствуют последовательности назначений, выполняемых в текстовом порядке, в котором они отображаются в объявлении класса.The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. Если в классе существует статический конструктор (Статические конструкторы), выполнение инициализаторов статических полей происходит непосредственно перед выполнением этого статического конструктора.If a static constructor (Static constructors) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. В противном случае инициализаторы статических полей выполняются во время, зависящее от реализации, до первого использования статического поля этого класса.Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class. ПримерThe example

using System;

class Test 
{ 
    static void Main() {
        Console.WriteLine("{0} {1}", 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");
}

может создать либо выходные данные:might produce either the output:

Init A
Init B
1 1

или выходные данные:or the output:

Init B
Init A
1 1

Поскольку выполнение инициализатора Xи инициализатора Yмогут происходить в любом порядке; они ограничиваются только до ссылок на эти поля.because the execution of X's initializer and Y's initializer could occur in either order; they are only constrained to occur before the references to those fields. Однако в этом примере:However, in the example:

using System;

class Test
{
    static void Main() {
        Console.WriteLine("{0} {1}", 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");
}

выходные данные должны быть:the output must be:

Init B
Init A
1 1

Поскольку правила при выполнении статических конструкторов (как определено в статических конструкторах) предоставляют статический конструктор B(и, следовательно, Bинициализаторы статических полей) перед статическими Aинициализаторами конструктора и полей должны выполняться раньше.because the rules for when static constructors execute (as defined in Static constructors) provide that B's static constructor (and hence B's static field initializers) must run before A's static constructor and field initializers.

Инициализация поля экземпляраInstance field initialization

Инициализаторы переменных полей экземпляров класса соответствуют последовательности назначений, которые выполняются сразу после входа в любой из конструкторов экземпляров этого класса (инициализаторов конструктора).The instance field variable initializers of a class correspond to a sequence of assignments that are executed immediately upon entry to any one of the instance constructors (Constructor initializers) of that class. Инициализаторы переменных выполняются в текстовом порядке, в котором они отображаются в объявлении класса.The variable initializers are executed in the textual order in which they appear in the class declaration. Процесс создания и инициализации экземпляров класса описан далее в разделе конструкторы экземпляров.The class instance creation and initialization process is described further in Instance constructors.

Инициализатор переменных для поля экземпляра не может ссылаться на создаваемый экземпляр.A variable initializer for an instance field cannot reference the instance being created. Таким образом, это ошибка времени компиляции для ссылки на this в инициализаторе переменных, так как это ошибка времени компиляции, когда инициализатор переменной ссылается на любой член экземпляра через simple_name.Thus, it is a compile-time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple_name. в примереIn the example

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

инициализатор переменной для y приводит к ошибке во время компиляции, так как он ссылается на элемент создаваемого экземпляра.the variable initializer for y results in a compile-time error because it references a member of the instance being created.

МетодыMethods

Метод — это член, реализующий вычисление или действие, которое может выполнять объект или класс.A method is a member that implements a computation or action that can be performed by an object or class. Методы объявляются с помощью method_declarations:Methods are declared using method_declarations:

method_declaration
    : method_header method_body
    ;

method_header
    : attributes? method_modifier* 'partial'? return_type member_name type_parameter_list?
      '(' formal_parameter_list? ')' type_parameter_constraints_clause*
    ;

method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'async'
    | method_modifier_unsafe
    ;

return_type
    : type
    | 'void'
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

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

Method_declaration может включать набор атрибутов (атрибутов) и допустимое сочетание четырех модификаторов доступа (модификаторы доступа), new (Модификатор new), static (статический метод и методы экземпляра), virtual (виртуальные методы), override (методы переопределения), sealed (Запечатанные методы), abstract (абстрактные методы) и модификаторы extern (Внешние методы).A method_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.

Объявление имеет допустимое сочетание модификаторов, если выполняются все приведенные ниже условия.A declaration has a valid combination of modifiers if all of the following are true:

  • Объявление включает допустимое сочетание модификаторов доступа (модификаторы доступа).The declaration includes a valid combination of access modifiers (Access modifiers).
  • Объявление не включает один и тот же модификатор несколько раз.The declaration does not include the same modifier multiple times.
  • Объявление включает не более одного из следующих модификаторов: static, virtualи override.The declaration includes at most one of the following modifiers: static, virtual, and override.
  • Объявление включает не более одного из следующих модификаторов: new и override.The declaration includes at most one of the following modifiers: new and override.
  • Если объявление включает модификатор abstract, то объявление не включает следующие модификаторы: static, virtual, sealed или extern.If the declaration includes the abstract modifier, then the declaration does not include any of the following modifiers: static, virtual, sealed or extern.
  • Если объявление включает модификатор private, то объявление не включает следующие модификаторы: virtual, overrideили abstract.If the declaration includes the private modifier, then the declaration does not include any of the following modifiers: virtual, override, or abstract.
  • Если объявление включает модификатор sealed, то объявление также включает модификатор override.If the declaration includes the sealed modifier, then the declaration also includes the override modifier.
  • Если объявление содержит модификатор partial, то он не включает следующие модификаторы: new, public, protected, internal, private, virtual, sealed, override, abstractили extern.If the declaration includes the partial modifier, then it does not include any of the following modifiers: new, public, protected, internal, private, virtual, sealed, override, abstract, or extern.

Метод с модификатором async является асинхронной функцией и соответствует правилам, описанным в статье асинхронные функции.A method that has the async modifier is an async function and follows the rules described in Async functions.

Return_type объявления метода указывает тип значения, вычисленного и возвращаемого методом.The return_type of a method declaration specifies the type of the value computed and returned by the method. Return_type void, если метод не возвращает значение.The return_type is void if the method does not return a value. Если объявление содержит модификатор partial, то возвращаемый тип должен быть void.If the declaration includes the partial modifier, then the return type must be void.

MEMBER_NAME указывает имя метода.The member_name specifies the name of the method. Если метод не является явной реализацией члена интерфейса (явных реализаций члена интерфейса), MEMBER_NAME является просто идентификатором.Unless the method is an explicit interface member implementation (Explicit interface member implementations), the member_name is simply an identifier. Для явной реализации члена интерфейса MEMBER_NAME состоит из interface_type , за которым следует "." и идентификатор.For an explicit interface member implementation, the member_name consists of an interface_type followed by a "." and an identifier.

Необязательный type_parameter_list указывает параметры типа метода (Параметры типа).The optional type_parameter_list specifies the type parameters of the method (Type parameters). Если указан type_parameter_list , метод является универсальным методом.If a type_parameter_list is specified the method is a generic method. Если метод имеет модификатор extern, то type_parameter_list не может быть указан.If the method has an extern modifier, a type_parameter_list cannot be specified.

Необязательный formal_parameter_list указывает параметры метода (Параметры метода).The optional formal_parameter_list specifies the parameters of the method (Method parameters).

Необязательные type_parameter_constraints_clauses задают ограничения для отдельных параметров типа (ограничения параметра типа) и могут указываться только в том случае, если также указана type_parameter_list , а метод не имеет модификатора override.The optional type_parameter_constraints_clauses specify constraints on individual type parameters (Type parameter constraints) and may only be specified if a type_parameter_list is also supplied, and the method does not have an override modifier.

Return_type и каждый из типов, на которые ссылается formal_parameter_list метода, должен иметь по крайней мере такой же уровень доступности, как и сам метод (ограничения доступности).The return_type and each of the types referenced in the formal_parameter_list of a method must be at least as accessible as the method itself (Accessibility constraints).

Method_body является точкой с запятой, телом оператора или телом выражения.The method_body is either a semicolon, a statement body or an expression body. Тело оператора состоит из блока, в котором указываются операторы, выполняемые при вызове метода.A statement body consists of a block, which specifies the statements to execute when the method is invoked. Тело выражения состоит из =>, за которым следует выражение и точка с запятой, и обозначает одно выражение, выполняемое при вызове метода.An expression body consists of => followed by an expression and a semicolon, and denotes a single expression to perform when the method is invoked.

Для методов abstract и extern method_body состоит только из точки с запятой.For abstract and extern methods, the method_body consists simply of a semicolon. Для partial методов method_body может состоять из точки с запятой, тела блока или тела выражения.For partial methods the method_body may consist of either a semicolon, a block body or an expression body. Для всех остальных методов method_body является либо телом блока, либо телом выражения.For all other methods, the method_body is either a block body or an expression body.

Если method_body состоит из точки с запятой, то объявление не может включать модификатор async.If the method_body consists of a semicolon, then the declaration may not include the async modifier.

Имя, список параметров типа и список формальных параметров метода определяют сигнатуру (сигнатуры и перегрузку) метода.The name, the type parameter list and the formal parameter list of a method define the signature (Signatures and overloading) of the method. В частности, сигнатура метода состоит из его имени, числа параметров типа, числа, модификаторов и типов своих формальных параметров.Specifically, the signature of a method consists of its name, the number of type parameters and the number, modifiers, and types of its formal parameters. Для этих целей любой параметр типа метода, который выполняется в типе формального параметра, идентифицируется не по имени, а по его порядковому номеру в списке аргументов типа метода. Возвращаемый тип не является частью сигнатуры метода и не является именами параметров типа или формальных параметров.For these purposes, any type parameter of the method that occurs in the type of a formal parameter is identified not by its name, but by its ordinal position in the type argument list of the method.The return type is not part of a method's signature, nor are the names of the type parameters or the formal parameters.

Имя метода должно отличаться от имен всех остальных не являющихся методами, объявленными в том же классе.The name of a method must differ from the names of all other non-methods declared in the same class. Кроме того, сигнатура метода должна отличаться от сигнатур всех других методов, объявленных в том же классе, и два метода, объявленных в одном классе, не могут иметь сигнатуры, отличающиеся только ref и out.In addition, the signature of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref and out.

Type_parameterметода находятся в области во всех method_declarationхи могут использоваться для формирования типов в пределах этой области в return_type, method_bodyи type_parameter_constraints_clause, но не в атрибутах.The method's type_parameters are in scope throughout the method_declaration, and can be used to form types throughout that scope in return_type, method_body, and type_parameter_constraints_clauses but not in attributes.

Все формальные параметры и параметры типа должны иметь разные имена.All formal parameters and type parameters must have different names.

Параметры методаMethod parameters

Параметры метода (если таковые имеются) объявляются formal_parameter_listомметода.The parameters of a method, if any, are declared by the method's formal_parameter_list.

formal_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
    : 'ref'
    | 'out'
    | 'this'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

Список формальных параметров состоит из одного или нескольких параметров, разделенных запятыми, в которых только последний может быть parameter_array.The formal parameter list consists of one or more comma-separated parameters of which only the last may be a parameter_array.

Fixed_parameter состоит из необязательного набора атрибутов (атрибутов), необязательного модификатора ref, out или this, типа, идентификатора и необязательного default_argument.A fixed_parameter consists of an optional set of attributes (Attributes), an optional ref, out or this modifier, a type, an identifier and an optional default_argument. Каждый fixed_parameter объявляет параметр заданного типа с заданным именем.Each fixed_parameter declares a parameter of the given type with the given name. Модификатор this обозначает метод как метод расширения и разрешается только в первом параметре статического метода.The this modifier designates the method as an extension method and is only allowed on the first parameter of a static method. Методы расширения описаны в разделе методы расширения.Extension methods are further described in Extension methods.

Fixed_parameter с default_argument называется необязательным параметром, а fixed_parameter без default_argument является обязательным параметром.A fixed_parameter with a default_argument is known as an optional parameter, whereas a fixed_parameter without a default_argument is a required parameter. Обязательный параметр не может присутствовать после необязательного параметра в formal_parameter_list.A required parameter may not appear after an optional parameter in a formal_parameter_list.

Параметр ref или out не может иметь default_argument.A ref or out parameter cannot have a default_argument. Выражение в default_argument должно быть одним из следующих:The expression in a default_argument must be one of the following:

  • constant_expressiona constant_expression
  • выражение new S(), где S является типом значенияan expression of the form new S() where S is a value type
  • выражение default(S), где S является типом значенияan expression of the form default(S) where S is a value type

Выражение должно быть неявно преобразуемым с помощью удостоверения или преобразования, допускающего значение null, в тип параметра.The expression must be implicitly convertible by an identity or nullable conversion to the type of the parameter.

Если необязательные параметры встречаются в реализующем объявлении разделяемого метода (разделяемые методы), явной реализации члена интерфейса (явных реализаций элементов интерфейса) или в объявлениииндексаторас одним параметром, компилятор должен выдать предупреждение, так как эти члены никогда не могут быть вызваны способом, который позволяет опускать аргументы.If optional parameters occur in an implementing partial method declaration (Partial methods) , an explicit interface member implementation (Explicit interface member implementations) or in a single-parameter indexer declaration (Indexers) the compiler should give a warning, since these members can never be invoked in a way that permits arguments to be omitted.

Parameter_array состоит из необязательного набора атрибутов (атрибутов), модификатора params, array_typeи идентификатора.A parameter_array consists of an optional set of attributes (Attributes), a params modifier, an array_type, and an identifier. Массив параметров объявляет один параметр заданного типа массива с заданным именем.A parameter array declares a single parameter of the given array type with the given name. Array_type массива параметров должен быть одномерным массивом (типами массивов).The array_type of a parameter array must be a single-dimensional array type (Array types). При вызове метода массив параметров позволяет указать либо один аргумент заданного типа массива, либо нуль-или более аргументов типа элемента массива.In a method invocation, a parameter array permits either a single argument of the given array type to be specified, or it permits zero or more arguments of the array element type to be specified. Массивы параметров описаны далее в разделе массивы параметров.Parameter arrays are described further in Parameter arrays.

Parameter_array может возникать после необязательного параметра, но не может иметь значение по умолчанию. при этом parameter_array опущенный массив будет создан в случае, если это не так.A parameter_array may occur after an optional parameter, but cannot have a default value -- the omission of arguments for a parameter_array would instead result in the creation of an empty array.

В следующем примере показаны различные виды параметров.The following example illustrates different kinds of parameters:

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

В formal_parameter_list для M``i является обязательным параметром ref, d является обязательным параметром значения, b, s, o и t являются необязательными параметрами значения, а a — массивом параметров.In the formal_parameter_list for M, i is a required ref parameter, d is a required value parameter, b, s, o and t are optional value parameters and a is a parameter array.

Объявление метода создает отдельное пространство объявления для параметров, параметров типа и локальных переменных.A method declaration creates a separate declaration space for parameters, type parameters and local variables. Имена вводятся в это пространство объявления с помощью списка параметров типа, а также списка формальных параметров метода и объявлений локальных переменных в блоке метода.Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. Является ошибкой, когда два члена области объявления метода имеют одно и то же имя.It is an error for two members of a method declaration space to have the same name. Является ошибкой для пространства объявления метода и пространства объявления локальной переменной в области вложенного объявления, чтобы содержать элементы с одинаковым именем.It is an error for the method declaration space and the local variable declaration space of a nested declaration space to contain elements with the same name.

Вызов метода (вызовы метода) создает копию, относящуюся к этому вызову, для формальных параметров и локальных переменных метода, а список аргументов вызова присваивает значения или ссылки на переменные только что созданных формальных параметров.A method invocation (Method invocations) creates a copy, specific to that invocation, of the formal parameters and local variables of the method, and the argument list of the invocation assigns values or variable references to the newly created formal parameters. В блоке метода на формальные параметры можно ссылаться по идентификаторам в выражениях simple_name (простые имена).Within the block of a method, formal parameters can be referenced by their identifiers in simple_name expressions (Simple names).

Существует четыре типа формальных параметров:There are four kinds of formal parameters:

  • Параметры значения, которые объявляются без модификаторов.Value parameters, which are declared without any modifiers.
  • Ссылочные параметры, которые объявляются с помощью модификатора ref.Reference parameters, which are declared with the ref modifier.
  • Выходные параметры, которые объявляются с помощью модификатора out.Output parameters, which are declared with the out modifier.
  • Массивы параметров, которые объявляются с помощью модификатора params.Parameter arrays, which are declared with the params modifier.

Как описано в разделе сигнатуры и перегрузка, модификаторы ref и out являются частью сигнатуры метода, но модификатором params не является.As described in Signatures and overloading, the ref and out modifiers are part of a method's signature, but the params modifier is not.

Параметры значенияValue parameters

Параметр, объявленный без модификаторов, является параметром значения.A parameter declared with no modifiers is a value parameter. Параметр значения соответствует локальной переменной, которая получает исходное значение из соответствующего аргумента, заданного в вызове метода.A value parameter corresponds to a local variable that gets its initial value from the corresponding argument supplied in the method invocation.

Если формальный параметр является параметром значения, соответствующий аргумент в вызове метода должен быть выражением, которое неявно преобразует (неявные преобразования) в формальный тип параметра.When a formal parameter is a value parameter, the corresponding argument in a method invocation must be an expression that is implicitly convertible (Implicit conversions) to the formal parameter type.

Методу разрешено назначать новые значения параметру значения.A method is permitted to assign new values to a value parameter. Такие назначения влияют только на локальное место хранения, представленное параметром value — они не влияют на фактический аргумент, заданный в вызове метода.Such assignments only affect the local storage location represented by the value parameter—they have no effect on the actual argument given in the method invocation.

Параметры ссылокReference parameters

Параметр, объявленный с модификатором ref, является ссылочным параметром.A parameter declared with a ref modifier is a reference parameter. В отличие от параметра значения, параметр ссылки не создает новое место хранения.Unlike a value parameter, a reference parameter does not create a new storage location. Вместо этого ссылочный параметр представляет то же место хранения, что и переменная, заданная в качестве аргумента в вызове метода.Instead, a reference parameter represents the same storage location as the variable given as the argument in the method invocation.

Если формальный параметр является ссылочным параметром, соответствующий аргумент в вызове метода должен состоять из ключевого слова ref за которым следует variable_reference (точные правила для определения определенного присваивания) того же типа, что и формальный параметр.When a formal parameter is a reference parameter, the corresponding argument in a method invocation must consist of the keyword ref followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. Переменная должна быть явно назначена, прежде чем ее можно будет передать в качестве ссылочного параметра.A variable must be definitely assigned before it can be passed as a reference parameter.

В методе ссылочный параметр всегда считается определенно присвоенным.Within a method, a reference parameter is always considered definitely assigned.

Метод, объявленный как итератор (итераторы), не может иметь ссылочных параметров.A method declared as an iterator (Iterators) cannot have reference parameters.

ПримерThe example

using System;

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 = {0}, j = {1}", i, j);
    }
}

выводятся следующие выходные данныеproduces the output

i = 2, j = 1

Для вызова Swap в Main``x представляет i и y представляет j.For the invocation of Swap in Main, x represents i and y represents j. Таким же результатом, вызов приводит к переключению значений i и j.Thus, the invocation has the effect of swapping the values of i and j.

В методе, который принимает ссылочные параметры, несколько имен могут представлять одно и то же место хранения.In a method that takes reference parameters it is possible for multiple names to represent the same storage location. в примереIn the example

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);
    }
}

вызов F в G передает ссылку на s как для a, так и для b.the invocation of F in G passes a reference to s for both a and b. Таким же для этого вызова имена s, aи b все ссылаются на одно и то же место хранения, и все три назначения изменяют поле экземпляра s.Thus, for that invocation, the names s, a, and b all refer to the same storage location, and the three assignments all modify the instance field s.

Параметры выводаOutput parameters

Параметр, объявленный с модификатором out, является выходным параметром.A parameter declared with an out modifier is an output parameter. Аналогично параметру ссылки параметр OUTPUT не создает новое место хранения.Similar to a reference parameter, an output parameter does not create a new storage location. Вместо этого выходной параметр представляет то же место хранения, что и переменная, заданная в качестве аргумента в вызове метода.Instead, an output parameter represents the same storage location as the variable given as the argument in the method invocation.

Если формальный параметр является выходным, соответствующий аргумент в вызове метода должен состоять из ключевого слова out за которым следует variable_reference (точные правила для определения определенного присваивания) того же типа, что и формальный параметр.When a formal parameter is an output parameter, the corresponding argument in a method invocation must consist of the keyword out followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. Переменная не должна быть определенно назначена, прежде чем ее можно будет передать в качестве выходного параметра, но после вызова, в котором переменная была передана в качестве выходного параметра, переменная считается определенно присвоенной.A variable need not be definitely assigned before it can be passed as an output parameter, but following an invocation where a variable was passed as an output parameter, the variable is considered definitely assigned.

В методе, как и в локальной переменной, параметр OUTPUT изначально считается неназначенным и должен быть определенно назначен до использования его значения.Within a method, just like a local variable, an output parameter is initially considered unassigned and must be definitely assigned before its value is used.

Каждый выходной параметр метода должен быть определенно назначен перед возвратом из метода.Every output parameter of a method must be definitely assigned before the method returns.

Метод, объявленный как разделяемый метод (разделяемые методы) или итератор (итераторы), не может иметь выходных параметров.A method declared as a partial method (Partial methods) or an iterator (Iterators) cannot have output parameters.

Выходные параметры обычно используются в методах, создающих несколько возвращаемых значений.Output parameters are typically used in methods that produce multiple return values. Например:For example:

using System;

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);
    }
}

В примере выводится результат:The example produces the output:

c:\Windows\System\
hello.txt

Обратите внимание, что переменные dir и name можно отменить, прежде чем они передаются SplitPath, и что они считаются определенно назначенными после вызова.Note that the dir and name variables can be unassigned before they are passed to SplitPath, and that they are considered definitely assigned following the call.

Массивы параметровParameter arrays

Параметр, объявленный с модификатором params, является массивом параметров.A parameter declared with a params modifier is a parameter array. Если список формальных параметров содержит массив параметров, то он должен быть последним параметром в списке и должен иметь одномерный массив.If a formal parameter list includes a parameter array, it must be the last parameter in the list and it must be of a single-dimensional array type. Например, типы string[] и string[][] могут использоваться в качестве типа массива параметров, но тип string[,] не может.For example, the types string[] and string[][] can be used as the type of a parameter array, but the type string[,] can not. Нельзя сочетать модификатор params с модификаторами ref и out.It is not possible to combine the params modifier with the modifiers ref and out.

Массив параметров позволяет задавать аргументы одним из двух способов при вызове метода:A parameter array permits arguments to be specified in one of two ways in a method invocation:

  • Аргумент, заданный для массива параметров, может быть одиночным выражением, неявно преобразуемым (неявными преобразованиями) в тип массива параметров.The argument given for a parameter array can be a single expression that is implicitly convertible (Implicit conversions) to the parameter array type. В этом случае массив параметров работает точно так же, как параметр значения.In this case, the parameter array acts precisely like a value parameter.
  • Кроме того, вызов может указать ноль или более аргументов для массива параметров, где каждый аргумент представляет собой выражение, которое неявно преобразуется (неявные преобразования) в тип элемента массива параметров.Alternatively, the invocation can specify zero or more arguments for the parameter array, where each argument is an expression that is implicitly convertible (Implicit conversions) to the element type of the parameter array. В этом случае вызов создает экземпляр типа массива параметров с длиной, соответствующей количеству аргументов, инициализирует элементы экземпляра массива с заданными значениями аргументов и использует только что созданный экземпляр массива в качестве фактического параметр.In this case, the invocation creates an instance of the parameter array type with a length corresponding to the number of arguments, initializes the elements of the array instance with the given argument values, and uses the newly created array instance as the actual argument.

Если не разрешить переменное число аргументов в вызове, массив параметров в точности эквивалентен параметру значения (параметрам значения) того же типа.Except for allowing a variable number of arguments in an invocation, a parameter array is precisely equivalent to a value parameter (Value parameters) of the same type.

ПримерThe example

using System;

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

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

выводятся следующие выходные данныеproduces the output

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

Первый вызов F просто передает массив a как параметр значения.The first invocation of F simply passes the array a as a value parameter. Второй вызов F автоматически создает int[] с указанными значениями элементов с четырьмя элементами и передает этот экземпляр массива в качестве параметра значения.The second invocation of F automatically creates a four-element int[] with the given element values and passes that array instance as a value parameter. Аналогичным образом, третий вызов F создает нулевой элемент int[] и передает этот экземпляр в качестве параметра значения.Likewise, the third invocation of F creates a zero-element int[] and passes that instance as a value parameter. Второй и третий вызовы в точности эквивалентны написанию:The second and third invocations are precisely equivalent to writing:

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

При выполнении разрешения перегрузки метод с массивом параметров может быть применим как в нормальной форме, так и в его развернутой форме (применимый член функции).When performing overload resolution, a method with a parameter array may be applicable either in its normal form or in its expanded form (Applicable function member). Расширенная форма метода доступна только в том случае, если нормальная форма метода неприменима и только если применимый метод с той же сигнатурой, что и развернутая форма, еще не объявлен в том же типе.The expanded form of a method is available only if the normal form of the method is not applicable and only if an applicable method with the same signature as the expanded form is not already declared in the same type.

ПримерThe example

using System;

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);
    }
}

выводятся следующие выходные данныеproduces the output

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

В примере две из возможных расширенных форм метода с массивом параметров уже включены в класс как обычные методы.In the example, two of the possible expanded forms of the method with a parameter array are already included in the class as regular methods. Поэтому эти расширенные формы не учитываются при выполнении разрешения перегрузки, а первый и третий вызовы методов, соответственно, выбирают обычные методы.These expanded forms are therefore not considered when performing overload resolution, and the first and third method invocations thus select the regular methods. Если класс объявляет метод с массивом параметров, он часто включает некоторые расширенные формы в виде обычных методов.When a class declares a method with a parameter array, it is not uncommon to also include some of the expanded forms as regular methods. Таким образом можно избежать выделения экземпляра массива, который происходит при вызове расширенной формы метода с массивом параметров.By doing so it is possible to avoid the allocation of an array instance that occurs when an expanded form of a method with a parameter array is invoked.

Если тип массива параметров object[], возникает потенциальная неоднозначность между нормальной формой метода и расходной формой для одного параметра object.When the type of a parameter array is object[], a potential ambiguity arises between the normal form of the method and the expended form for a single object parameter. Причина неоднозначности заключается в том, что object[] неявно преобразуется в тип object.The reason for the ambiguity is that an object[] is itself implicitly convertible to type object. Неоднозначность не представляет проблем, так как ее можно разрешить, вставив приведение при необходимости.The ambiguity presents no problem, however, since it can be resolved by inserting a cast if needed.

ПримерThe example

using System;

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);
    }
}

выводятся следующие выходные данныеproduces the output

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

В первом и последнем вызовах Fобычная форма F применима, поскольку существует неявное преобразование из типа аргумента в тип параметра (оба типа имеют тип object[]).In the first and last invocations of F, the normal form of F is applicable because an implicit conversion exists from the argument type to the parameter type (both are of type object[]). Таким образом, при разрешении перегрузки выбирается обычная форма F, а аргумент передается как параметр обычного значения.Thus, overload resolution selects the normal form of F, and the argument is passed as a regular value parameter. Во втором и третьем вызовах обычная форма F неприменима, так как не существует неявного преобразования из типа аргумента в тип параметра (тип object не может быть неявно преобразован в тип object[]).In the second and third invocations, the normal form of F is not applicable because no implicit conversion exists from the argument type to the parameter type (type object cannot be implicitly converted to type object[]). Однако развернутая форма F применима, поэтому она выбирается с помощью разрешения перегрузки.However, the expanded form of F is applicable, so it is selected by overload resolution. В результате при вызове создается object[] с одним элементом, а единственный элемент массива инициализируется с заданным значением аргумента (сам по себе является ссылкой на object[]).As a result, a one-element object[] is created by the invocation, and the single element of the array is initialized with the given argument value (which itself is a reference to an object[]).

Статические методы и методы экземпляраStatic and instance methods

Если объявление метода содержит модификатор static, этот метод называется статическим методом.When a method declaration includes a static modifier, that method is said to be a static method. Если модификатор static отсутствует, метод считается методом экземпляра.When no static modifier is present, the method is said to be an instance method.

Статический метод не работает с конкретным экземпляром, и это ошибка времени компиляции для ссылки на this в статическом методе.A static method does not operate on a specific instance, and it is a compile-time error to refer to this in a static method.

Метод экземпляра работает с заданным экземпляром класса, и доступ к этому экземпляру можно получить как this (этот доступ).An instance method operates on a given instance of a class, and that instance can be accessed as this (This access).

При указании ссылки на метод в member_access (доступ к члену) в форме E.M, если M является статическим методом, E должен обменяться типом, содержащим M, а если M является методом экземпляра, E должен обменяться экземпляром типа, содержащего M.When a method is referenced in a member_access (Member access) of the form E.M, if M is a static method, E must denote a type containing M, and if M is an instance method, E must denote an instance of a type containing M.

Различия между статическими и членами экземпляра обсуждаются далее в статических членах и экземплярах.The differences between static and instance members are discussed further in Static and instance members.

Виртуальные методыVirtual methods

Если объявление метода экземпляра содержит модификатор virtual, этот метод называется виртуальным методом.When an instance method declaration includes a virtual modifier, that method is said to be a virtual method. Если модификатор virtual отсутствует, метод считается невиртуальным методом.When no virtual modifier is present, the method is said to be a non-virtual method.

Реализация невиртуального метода является инвариантной: реализация аналогична тому, вызывается ли метод на экземпляре класса, в котором он объявлен, или на экземпляре производного класса.The implementation of a non-virtual method is invariant: The implementation is the same whether the method is invoked on an instance of the class in which it is declared or an instance of a derived class. В отличие от этого, реализация виртуального метода может быть заменена производными классами.In contrast, the implementation of a virtual method can be superseded by derived classes. Процесс замены реализации наследуемого виртуального метода называется переопределением этого метода (методов переопределения).The process of superseding the implementation of an inherited virtual method is known as overriding that method (Override methods).

При вызове виртуального метода тип времени выполнения экземпляра, для которого выполняется вызов, определяет фактическую реализацию метода для вызова.In a virtual method invocation, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. В невиртуальном вызове метода тип времени компиляции экземпляра — это определяющий фактор.In a non-virtual method invocation, the compile-time type of the instance is the determining factor. В точных условиях, когда метод с именем N вызывается со списком аргументов A в экземпляре с типом времени компиляции C и R типа времени выполнения (где R либо C, либо как класс, производный от C), вызов обрабатывается следующим образом:In precise terms, when a method named N is invoked with an argument list A on an instance with a compile-time type C and a run-time type R (where R is either C or a class derived from C), the invocation is processed as follows:

  • Во-первых, для C, Nи Aприменяется разрешение перегрузки, чтобы выбрать определенный метод M из набора методов, объявленных в и унаследованных C.First, overload resolution is applied to C, N, and A, to select a specific method M from the set of methods declared in and inherited by C. Это описано в разделе вызовы методов.This is described in Method invocations.
  • Затем, если M является невиртуальным методом, вызывается M.Then, if M is a non-virtual method, M is invoked.
  • В противном случае M является виртуальным методом, и вызывается самая большая Производная реализация M по отношению к R.Otherwise, M is a virtual method, and the most derived implementation of M with respect to R is invoked.

Для каждого виртуального метода, объявленного в или унаследованном от класса, существует наиболее Производная реализация метода относительно этого класса.For every virtual method declared in or inherited by a class, there exists a most derived implementation of the method with respect to that class. Наиболее Производная реализация виртуального метода M относительно класса R определяется следующим образом:The most derived implementation of a virtual method M with respect to a class R is determined as follows:

  • Если R содержит объявление virtual M, это самая Производная реализация M.If R contains the introducing virtual declaration of M, then this is the most derived implementation of M.
  • В противном случае, если R содержит override M, это самая Производная реализация M.Otherwise, if R contains an override of M, then this is the most derived implementation of M.
  • В противном случае самая Производная реализация M с учетом R совпадает с самой производной реализацией M в отношении прямого базового класса R.Otherwise, the most derived implementation of M with respect to R is the same as the most derived implementation of M with respect to the direct base class of R.

В следующем примере показаны различия между виртуальными и невиртуальными методами.The following example illustrates the differences between virtual and non-virtual methods:

using System;

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

    public virtual void G() { Console.WriteLine("A.G"); }
}

class B: A
{
    new public 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 вводит невиртуальный метод F и виртуальный метод G.In the example, A introduces a non-virtual method F and a virtual method G. Класс B вводит новый невиртуальный метод F, тем самым скрывая наследуемые F, а также переопределяет унаследованный метод G.The class B introduces a new non-virtual method F, thus hiding the inherited F, and also overrides the inherited method G. В примере выводится результат:The example produces the output:

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

Обратите внимание, что оператор a.G() вызывает B.G, а не A.G.Notice that the statement a.G() invokes B.G, not A.G. Это происходит потому, что тип времени выполнения экземпляра (B), а не тип времени компиляции экземпляра (который A), определяет фактическую реализацию метода для вызова.This is because the run-time type of the instance (which is B), not the compile-time type of the instance (which is A), determines the actual method implementation to invoke.

Поскольку методы могут скрывать унаследованные методы, класс может содержать несколько виртуальных методов с одинаковой сигнатурой.Because methods are allowed to hide inherited methods, it is possible for a class to contain several virtual methods with the same signature. Это не представляет проблемы неоднозначности, так как все методы, кроме наиболее производного, скрыты.This does not present an ambiguity problem, since all but the most derived method are hidden. в примереIn the example

using System;

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

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

class C: B
{
    new public 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();
    }
}

классы C и D содержат два виртуальных метода с одинаковой сигнатурой: один, представленный A, и тот, который появился в C.the C and D classes contain two virtual methods with the same signature: The one introduced by A and the one introduced by C. Метод, представленный C, скрывает метод, наследуемый от A.The method introduced by C hides the method inherited from A. Таким способом, объявление переопределения в D переопределяет метод, представленный C, D и невозможно переопределять метод, представленный A.Thus, the override declaration in D overrides the method introduced by C, and it is not possible for D to override the method introduced by A. В примере выводится результат:The example produces the output:

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

Обратите внимание, что можно вызвать скрытый виртуальный метод, обратившись к экземпляру D через менее производный тип, в котором метод не скрыт.Note that it is possible to invoke the hidden virtual method by accessing an instance of D through a less derived type in which the method is not hidden.

Переопределение методовOverride methods

Если объявление метода экземпляра содержит модификатор override, то этот метод считается методом переопределения.When an instance method declaration includes an override modifier, the method is said to be an override method. Переопределяющий метод переопределяет наследуемый виртуальный метод с той же сигнатурой.An override method overrides an inherited virtual method with the same signature. Изначальное объявление виртуального метода создает новый метод, а переопределение этого метода создает специализированный виртуальный метод с новой реализацией взамен унаследованного виртуального метода.Whereas a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation of that method.

Метод, переопределенный объявлением override, называется переопределенным базовым методом.The method overridden by an override declaration is known as the overridden base method. Для метода переопределения M, объявленного в классе C, переопределенный базовый метод определяется путем проверки каждого типа базового класса в C, начиная с непосредственного типа базового класса C и продолжая каждый последовательный прямой тип базового класса, до тех пор, пока в данном типе базового класса не будет найден по крайней мере один доступный метод, который имеет ту же сигнатуру, что и M после замены аргументов типаFor an override method M declared in a class C, the overridden base method is determined by examining each base class type of C, starting with the direct base class type of C and continuing with each successive direct base class type, until in a given base class type at least one accessible method is located which has the same signature as M after substitution of type arguments. Для поиска переопределенного базового метода метод считается доступным, если он public, если он protected, если он protected internalили internal и объявлен в той же программе, что и C.For the purposes of locating the overridden base method, a method is considered accessible if it is public, if it is protected, if it is protected internal, or if it is internal and declared in the same program as C.

Ошибка времени компиляции возникает, если для объявления переопределения не выполняются все следующие условия:A compile-time error occurs unless all of the following are true for an override declaration:

  • Переопределенный базовый метод можно найти, как описано выше.An overridden base method can be located as described above.
  • Существует только один переопределенный базовый метод.There is exactly one such overridden base method. Это ограничение действует только в том случае, если тип базового класса является сконструированным типом, где Замена аргументов типа делает сигнатуру двух методов одинаковой.This restriction has effect only if the base class type is a constructed type where the substitution of type arguments makes the signature of two methods the same.
  • Переопределенный базовый метод является виртуальным, абстрактным или переопределяемым методом.The overridden base method is a virtual, abstract, or override method. Иными словами, переопределенный базовый метод не может быть статическим или не виртуальным.In other words, the overridden base method cannot be static or non-virtual.
  • Переопределенный базовый метод не является запечатанным методом.The overridden base method is not a sealed method.
  • Метод переопределения и переопределенный базовый метод имеют тот же тип возвращаемого значения.The override method and the overridden base method have the same return type.
  • Объявление переопределения и переопределенный базовый метод имеют одинаковую объявленную доступность.The override declaration and the overridden base method have the same declared accessibility. Иными словами, объявление переопределения не может изменить доступность виртуального метода.In other words, an override declaration cannot change the accessibility of the virtual method. Однако если переопределенный базовый метод защищен внутренним и объявлен в сборке, отличной от сборки, содержащей метод переопределения, то объявленная доступность метода переопределения должна быть защищена.However, if the overridden base method is protected internal and it is declared in a different assembly than the assembly containing the override method then the override method's declared accessibility must be protected.
  • В объявлении переопределения не указаны предложения Type-Parameter-rechecks.The override declaration does not specify type-parameter-constraints-clauses. Вместо этого ограничения наследуются от переопределенного базового метода.Instead the constraints are inherited from the overridden base method. Обратите внимание, что ограничения, которые являются параметрами типа в переопределенном методе, могут быть заменены аргументами типа в унаследованном ограничении.Note that constraints that are type parameters in the overridden method may be replaced by type arguments in the inherited constraint. Это может привести к ограничениям, которые не являются допустимыми, если явно заданы, такие как типы значений или Запечатанные типы.This can lead to constraints that are not legal when explicitly specified, such as value types or sealed types.

В следующем примере показано, как переопределяющие правила работают для универсальных классов.The following example demonstrates how the overriding rules work for generic classes:

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>
}

Объявление переопределения имеет доступ к переопределенному базовому методу с помощью base_access (базовый доступ).An override declaration can access the overridden base method using a base_access (Base access). в примереIn the example

class A
{
    int x;

    public virtual void PrintFields() {
        Console.WriteLine("x = {0}", x);
    }
}

class B: A
{
    int y;

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

вызов base.PrintFields() в B вызывает метод PrintFields, объявленный в A.the base.PrintFields() invocation in B invokes the PrintFields method declared in A. Base_access отключает механизм виртуального вызова и просто рассматривает базовый метод как невиртуальный метод.A base_access disables the virtual invocation mechanism and simply treats the base method as a non-virtual method. Если вызов B был написан ((A)this).PrintFields(), он рекурсивно вызовет метод PrintFields, объявленный в B, а не тот, который объявлен в A, так как PrintFields является виртуальным, а тип времени выполнения ((A)this) B.Had the invocation in B been written ((A)this).PrintFields(), it would recursively invoke the PrintFields method declared in B, not the one declared in A, since PrintFields is virtual and the run-time type of ((A)this) is B.

Метод может переопределить другой метод только путем включения модификатора override.Only by including an override modifier can a method override another method. Во всех остальных случаях метод с такой же сигнатурой, как и у унаследованного метода, просто скрывает наследуемый метод.In all other cases, a method with the same signature as an inherited method simply hides the inherited method. в примереIn the example

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

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

метод F в B не включает модификатор override и, таким образом, не переопределяет метод F в A.the F method in B does not include an override modifier and therefore does not override the F method in A. Вместо этого метод F в B скрывает метод в A, и появляется предупреждение о том, что объявление не содержит модификатор new.Rather, the F method in B hides the method in A, and a warning is reported because the declaration does not include a new modifier.

в примереIn the example

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

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

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

метод F в B скрывает метод виртуального F, унаследованный от A.the F method in B hides the virtual F method inherited from A. Поскольку новый F в B имеет закрытый доступ, его область содержит только тело B и не распространяется на C.Since the new F in B has private access, its scope only includes the class body of B and does not extend to C. Таким образом, объявление F в C может переопределить F, унаследованное от A.Therefore, the declaration of F in C is permitted to override the F inherited from A.

Запечатанные методыSealed methods

Если объявление метода экземпляра содержит модификатор sealed, этот метод называется запечатанным методом.When an instance method declaration includes a sealed modifier, that method is said to be a sealed method. Если объявление метода экземпляра включает модификатор sealed, оно также должно включать модификатор override.If an instance method declaration includes the sealed modifier, it must also include the override modifier. Использование модификатора sealed не позволяет производному классу переопределять метод.Use of the sealed modifier prevents a derived class from further overriding the method.

в примереIn the example

using System;

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

    public virtual void G() {
        Console.WriteLine("A.G");
    }
}

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

    override public void G() {
        Console.WriteLine("B.G");
    } 
}

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

класс B предоставляет два метода переопределения: метод F с модификатором sealed и методом G, который не имеет.the class B provides two override methods: an F method that has the sealed modifier and a G method that does not. Bиспользование запечатанного modifier не позволяет C переопределять F.B's use of the sealed modifier prevents C from further overriding F.

Абстрактные методыAbstract methods

Если объявление метода экземпляра содержит модификатор abstract, этот метод называется абстрактным методом.When an instance method declaration includes an abstract modifier, that method is said to be an abstract method. Хотя абстрактный метод неявно также является виртуальным методом, он не может иметь модификатор virtual.Although an abstract method is implicitly also a virtual method, it cannot have the modifier virtual.

Объявление абстрактного метода представляет новый виртуальный метод, но не предоставляет реализацию этого метода.An abstract method declaration introduces a new virtual method but does not provide an implementation of that method. Вместо этого неабстрактные производные классы должны предоставлять собственную реализацию путем переопределения этого метода.Instead, non-abstract derived classes are required to provide their own implementation by overriding that method. Поскольку абстрактный метод не предоставляет фактической реализации, method_body абстрактного метода просто состоит из точки с запятой.Because an abstract method provides no actual implementation, the method_body of an abstract method simply consists of a semicolon.

Объявления абстрактных методов разрешены только в абстрактных классах (абстрактные классы).Abstract method declarations are only permitted in abstract classes (Abstract classes).

в примереIn the example

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);
    }
}

класс Shape определяет абстрактное понятие объекта Shape двумерная, который может рисовать сам себя.the Shape class defines the abstract notion of a geometrical shape object that can paint itself. Метод Paint является абстрактным, так как нет осмысленной реализации по умолчанию.The Paint method is abstract because there is no meaningful default implementation. Классы Ellipse и Box являются конкретными реализациями Shape.The Ellipse and Box classes are concrete Shape implementations. Поскольку эти классы не являются абстрактными, они должны переопределять метод Paint и обеспечивать фактическую реализацию.Because these classes are non-abstract, they are required to override the Paint method and provide an actual implementation.

Ошибка времени компиляции base_access (базовый доступ) для ссылки на абстрактный метод.It is a compile-time error for a base_access (Base access) to reference an abstract method. в примереIn the example

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

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

При вызове base.F() выводится ошибка времени компиляции, так как она ссылается на абстрактный метод.a compile-time error is reported for the base.F() invocation because it references an abstract method.

Объявление абстрактного метода может переопределить виртуальный метод.An abstract method declaration is permitted to override a virtual method. Это позволяет абстрактному классу принудительно выполнять повторную реализацию метода в производных классах и делает исходную реализацию метода недоступной.This allows an abstract class to force re-implementation of the method in derived classes, and makes the original implementation of the method unavailable. в примереIn the example

using System;

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");
    }
}

класс A объявляет виртуальный метод, класс B переопределяет этот метод абстрактным методом, а класс C Переопределяет абстрактный метод, чтобы обеспечить его собственную реализацию.class A declares a virtual method, class B overrides this method with an abstract method, and class C overrides the abstract method to provide its own implementation.

Внешние методыExternal methods

Если объявление метода содержит модификатор extern, этот метод называется внешним методом.When a method declaration includes an extern modifier, that method is said to be an external method. Внешние методы реализуются извне, как правило, с использованием языка, отличного от C#.External methods are implemented externally, typically using a language other than C#. Поскольку объявление внешнего метода не предоставляет фактической реализации, method_body внешнего метода просто состоит из точки с запятой.Because an external method declaration provides no actual implementation, the method_body of an external method simply consists of a semicolon. Внешний метод не может быть универсальным.An external method may not be generic.

Модификатор extern обычно используется в сочетании с атрибутом DllImport (взаимодействие с КОМПОНЕНТАМИ com и Win32), что позволяет реализовать внешние методы с помощью библиотек DLL (библиотек динамической компоновки).The extern modifier is typically used in conjunction with a DllImport attribute (Interoperation with COM and Win32 components), allowing external methods to be implemented by DLLs (Dynamic Link Libraries). Среда выполнения может поддерживать другие механизмы, с помощью которых можно предоставлять реализации внешних методов.The execution environment may support other mechanisms whereby implementations of external methods can be provided.

Если внешний метод включает атрибут DllImport, объявление метода также должно включать модификатор static.When an external method includes a DllImport attribute, the method declaration must also include a static modifier. В этом примере демонстрируется использование модификатора extern и атрибута DllImport.This example demonstrates the use of the extern modifier and the DllImport attribute:

using System.Text;
using System.Security.Permissions;
using System.Runtime.InteropServices;

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);
}

Разделяемые методы (краткий обзор)Partial methods (recap)

Если объявление метода содержит модификатор partial, этот метод считается разделяемым методом.When a method declaration includes a partial modifier, that method is said to be a partial method. Разделяемые методы могут объявляться только как члены разделяемых типов (разделяемые типы) и подвержены ряду ограничений.Partial methods can only be declared as members of partial types (Partial types), and are subject to a number of restrictions. Разделяемые методы описаны далее в разделе разделяемые методы.Partial methods are further described in Partial methods.

Методы расширенияExtension methods

Когда первый параметр метода включает модификатор this, этот метод называется методом расширения.When the first parameter of a method includes the this modifier, that method is said to be an extension method. Методы расширения могут быть объявлены только в неуниверсальных статических классах, не являющихся вложенными.Extension methods can only be declared in non-generic, non-nested static classes. Первый параметр метода расширения может не иметь модификаторов, отличных от this, а тип параметра не может быть типом указателя.The first parameter of an extension method can have no modifiers other than this, and the parameter type cannot be a pointer type.

Ниже приведен пример статического класса, который объявляет два метода расширения:The following is an example of a static class that declares two extension methods:

public static class Extensions
{
    public static int ToInt32(this string s) {
        return 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;
    }
}

Метод расширения является обычным статическим методом.An extension method is a regular static method. Кроме того, когда включающий его статический класс находится в области видимости, метод расширения может быть вызван с помощью синтаксиса вызова метода экземпляра (вызовы метода расширения) с использованием выражения получателя в качестве первого аргумента.In addition, where its enclosing static class is in scope, an extension method can be invoked using instance method invocation syntax (Extension method invocations), using the receiver expression as the first argument.

В следующей программе используются методы расширения, объявленные выше:The following program uses the extension methods declared above:

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

Метод Slice доступен на string[], а метод ToInt32 доступен в string, поскольку они были объявлены как методы расширения.The Slice method is available on the string[], and the ToInt32 method is available on string, because they have been declared as extension methods. Значение программы аналогично приведенному ниже, при использовании обычных вызовов статических методов:The meaning of the program is the same as the following, using ordinary static method calls:

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));
        }
    }
}

Тело методаMethod body

Method_body объявления метода состоит из тела блока, тела выражения или точки с запятой.The method_body of a method declaration consists of either a block body, an expression body or a semicolon.

Тип результата метода — void, если тип возвращаемого значения — void, или если метод является асинхронным, а тип возвращаемого значения — System.Threading.Tasks.Task.The result type of a method is void if the return type is void, or if the method is async and the return type is System.Threading.Tasks.Task. В противном случае тип результата неасинхронного метода является его типом возвращаемого значения, а тип результата асинхронного метода с возвращаемым типом System.Threading.Tasks.Task<T>T.Otherwise, the result type of a non-async method is its return type, and the result type of an async method with return type System.Threading.Tasks.Task<T> is T.

Если метод имеет void тип результата и тело блока, инструкции return (оператор return) в блоке не могут указывать выражение.When a method has a void result type and a block body, return statements (The return statement) in the block are not permitted to specify an expression. Если выполнение блока метода void обычно завершается нормально (т. е. Управление проходит из конца тела метода), этот метод просто возвращает его текущему вызывающему объекту.If execution of the block of a void method completes normally (that is, control flows off the end of the method body), that method simply returns to its current caller.

Если метод имеет void результат и тело выражения, выражение E должно быть statement_expression, а текст в точности эквивалентен тексту блока формы { E; }.When a method has a void result and an expression body, the expression E must be a statement_expression, and the body is exactly equivalent to a block body of the form { E; }.

Если метод имеет тип результата, отличный от void, и тело блока, каждая инструкция return в блоке должна указывать выражение, которое неявно преобразуется в тип результата.When a method has a non-void result type and a block body, each return statement in the block must specify an expression that is implicitly convertible to the result type. Конечная точка тела блока метода, возвращающего значение, должна быть недоступна.The endpoint of a block body of a value-returning method must not be reachable. Иными словами, в методе, возвращающем значение, с телом блока Управление не разрешается в конце тела метода.In other words, in a value-returning method with a block body, control is not permitted to flow off the end of the method body.

Если метод имеет тип результата, отличный от void, и тело выражения, выражение должно быть неявно преобразовано в тип результата, а текст в точности эквивалентен тексту блока формы { return E; }.When a method has a non-void result type and an expression body, the expression must be implicitly convertible to the result type, and the body is exactly equivalent to a block body of the form { return E; }.

в примереIn the example

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;
}

метод F, возвращающий значение, приводит к ошибке во время компиляции, так как управление может передаваться за конец тела метода.the value-returning F method results in a compile-time error because control can flow off the end of the method body. Методы G и H верны, так как все возможные пути выполнения заканчиваются оператором Return, который указывает возвращаемое значение.The G and H methods are correct because all possible execution paths end in a return statement that specifies a return value. Метод I правильный, так как его тело эквивалентно блоку операторов с единственным оператором Return в нем.The I method is correct, because its body is equivalent to a statement block with just a single return statement in it.

Перегрузка методовMethod overloading

Правила разрешения перегрузки метода описаны в разделе Определение типа.The method overload resolution rules are described in Type inference.

СвойстваProperties

Свойство — это элемент, предоставляющий доступ к характеристикам объекта или класса.A property is a member that provides access to a characteristic of an object or a class. Примерами свойств могут быть длина строки, размер шрифта, заголовок окна, имя клиента и т. д.Examples of properties include the length of a string, the size of a font, the caption of a window, the name of a customer, and so on. Свойства — это естественное расширение полей — они называются членами со связанными типами, а синтаксис для доступа к полям и свойствам одинаков.Properties are a natural extension of fields—both are named members with associated types, and the syntax for accessing fields and properties is the same. Однако свойства, в отличие от полей, не указывают места хранения.However, unlike fields, properties do not denote storage locations. Вместо этого свойства содержат методы доступа, в которых описаны инструкции для выполнения при чтении или записи значений.Instead, properties have accessors that specify the statements to be executed when their values are read or written. Таким способом свойства предоставляют механизм для сопоставления действий с чтением и записью атрибутов объекта. Кроме того, они позволяют вычислять такие атрибуты.Properties thus provide a mechanism for associating actions with the reading and writing of an object's attributes; furthermore, they permit such attributes to be computed.

Свойства объявляются с помощью property_declarations:Properties are declared using property_declarations:

property_declaration
    : attributes? property_modifier* type member_name property_body
    ;

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | property_modifier_unsafe
    ;

property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

Property_declaration может включать набор атрибутов (атрибутов) и допустимое сочетание четырех модификаторов доступа (модификаторы доступа), new (Модификатор new), static (статический метод и методы экземпляра), virtual (виртуальные методы), override (методы переопределения), sealed (Запечатанные методы), abstract (абстрактные методы) и модификаторы extern (Внешние методы).A property_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.

Объявления свойств подчиняются тем же правилам, что и объявления методов (методы) в отношении допустимых сочетаний модификаторов.Property declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers.

Тип объявления свойства указывает тип свойства, представленного объявлением, а MEMBER_NAME указывает имя свойства.The type of a property declaration specifies the type of the property introduced by the declaration, and the member_name specifies the name of the property. Если свойство не является явной реализацией члена интерфейса, MEMBER_NAME является просто идентификатором.Unless the property is an explicit interface member implementation, the member_name is simply an identifier. Для явной реализации члена интерфейса (явных реализаций члена интерфейса) member_name состоит из interface_type , за которым следует "." и идентификатор.For an explicit interface member implementation (Explicit interface member implementations), the member_name consists of an interface_type followed by a "." and an identifier.

Тип свойства должен иметь по крайней мере такой же уровень доступности, как и само свойство (ограничения доступности).The type of a property must be at least as accessible as the property itself (Accessibility constraints).

Property_body может состоять из тела метода доступа или тела выражения.A property_body may either consist of an accessor body or an expression body. В теле метода доступа accessor_declarations, который должен быть заключен в маркеры "{" и "}", объявите методы доступа (методы доступа) свойства.In an accessor body, accessor_declarations, which must be enclosed in "{" and "}" tokens, declare the accessors (Accessors) of the property. Методы доступа указывают исполняемые операторы, связанные с чтением и записью свойства.The accessors specify the executable statements associated with reading and writing the property.

Тело выражения, состоящее из =>, за которым следует выражение E и точка с запятой точно эквивалентна тексту оператора { get { return E; } }, и поэтому может использоваться только для указания свойств только для получения, в которых результат получения данных предоставляется одним выражением.An expression body consisting of => followed by an expression E and a semicolon is exactly equivalent to the statement body { get { return E; } }, and can therefore only be used to specify getter-only properties where the result of the getter is given by a single expression.

Property_initializer может быть предоставлен только для автоматически реализуемого свойства (автоматически реализованные свойства) и вызывает инициализацию базового поля таких свойств со значением, заданным в выражении.A property_initializer may only be given for an automatically implemented property (Automatically implemented properties), and causes the initialization of the underlying field of such properties with the value given by the expression.

Несмотря на то, что синтаксис для доступа к свойству такой же, как и для поля, свойство не классифицируется как переменная.Even though the syntax for accessing a property is the same as that for a field, a property is not classified as a variable. Таким образом, невозможно передать свойство в качестве аргумента ref или out.Thus, it is not possible to pass a property as a ref or out argument.

Если объявление свойства включает модификатор extern, свойство называется внешним свойством.When a property declaration includes an extern modifier, the property is said to be an external property. Поскольку объявление внешнего свойства не предоставляет фактической реализации, каждый из его accessor_declarations состоит из точки с запятой.Because an external property declaration provides no actual implementation, each of its accessor_declarations consists of a semicolon.

Статические и свойства экземпляраStatic and instance properties

Если объявление свойства включает модификатор static, свойство называется статическим свойством.When a property declaration includes a static modifier, the property is said to be a static property. Если модификатор static отсутствует, свойство называется свойством экземпляра.When no static modifier is present, the property is said to be an instance property.

Статическое свойство не связано с конкретным экземпляром и является ошибкой времени компиляции для ссылки на this в классах доступа статического свойства.A static property is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static property.

Свойство экземпляра связано с заданным экземпляром класса, и доступ к этому экземпляру можно получить как this (этот доступ) в классах доступа этого свойства.An instance property is associated with a given instance of a class, and that instance can be accessed as this (This access) in the accessors of that property.

Если на свойство имеется ссылка в member_access (доступ к члену) формы E.M, если M является статическим свойством, E должен обменяться типом, содержащим M, а если M является свойством экземпляра, E должен обозначить экземпляр типа, содержащего M.When a property is referenced in a member_access (Member access) of the form E.M, if M is a static property, E must denote a type containing M, and if M is an instance property, E must denote an instance of a type containing M.

Различия между статическими и членами экземпляра обсуждаются далее в статических членах и экземплярах.The differences between static and instance members are discussed further in Static and instance members.

Методы доступаAccessors

Accessor_declarations свойства указывают исполняемые операторы, связанные с чтением и записью этого свойства.The accessor_declarations of a property specify the executable statements associated with reading and writing that property.

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'
    ;

accessor_body
    : block
    | ';'
    ;

Объявления метода доступа состоят из get_accessor_declaration, set_accessor_declarationили и того и другого.The accessor declarations consist of a get_accessor_declaration, a set_accessor_declaration, or both. Каждое объявление метода доступа состоит из маркера get или set, за которым следует необязательный accessor_modifier и accessor_body.Each accessor declaration consists of the token get or set followed by an optional accessor_modifier and an accessor_body.

Использование accessor_modifiers регулируется следующими ограничениями.The use of accessor_modifiers is governed by the following restrictions:

  • Accessor_modifier не может использоваться в интерфейсе или в явной реализации члена интерфейса.An accessor_modifier may not be used in an interface or in an explicit interface member implementation.
  • Для свойства или индексатора, не имеющего модификатора override, accessor_modifier разрешается только в том случае, если свойство или индексатор имеет и get, и set, а затем разрешается только в одном из этих методов доступа.For a property or indexer that has no override modifier, an accessor_modifier is permitted only if the property or indexer has both a get and set accessor, and then is permitted only on one of those accessors.
  • Для свойства или индексатора, включающего модификатор override, метод доступа должен соответствовать accessor_modifier(при наличии) переопределенного метода доступа.For a property or indexer that includes an override modifier, an accessor must match the accessor_modifier, if any, of the accessor being overridden.
  • Accessor_modifier должен объявить доступность, которая является строго более строгой, чем объявленный уровень доступности свойства или индексатора.The accessor_modifier must declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. Точность:To be precise:
    • Если свойство или индексатор имеет объявленный уровень доступности public, accessor_modifier может быть либо protected internal, internal, protected, либо private.If the property or indexer has a declared accessibility of public, the accessor_modifier may be either protected internal, internal, protected, or private.
    • Если свойство или индексатор имеет объявленный уровень доступности protected internal, accessor_modifier может быть либо internal, protected, либо private.If the property or indexer has a declared accessibility of protected internal, the accessor_modifier may be either internal, protected, or private.
    • Если свойство или индексатор имеет объявленный уровень доступа internal или protected, accessor_modifier необходимо private.If the property or indexer has a declared accessibility of internal or protected, the accessor_modifier must be private.
    • Если свойство или индексатор имеет объявленный уровень доступа private, то accessor_modifier не могут использоваться.If the property or indexer has a declared accessibility of private, no accessor_modifier may be used.

Для abstract и extern свойства accessor_body для каждого указанного метода доступа — это просто точка с запятой.For abstract and extern properties, the accessor_body for each accessor specified is simply a semicolon. Неабстрактное свойство, не являющееся внешним, может иметь каждый accessor_body быть точкой с запятой, в этом случае это автоматически реализуемое свойство (автоматически реализуемые свойства).A non-abstract, non-extern property may have each accessor_body be a semicolon, in which case it is an automatically implemented property (Automatically implemented properties). Автоматически реализуемое свойство должно иметь по крайней мере метод доступа get.An automatically implemented property must have at least a get accessor. Для методов доступа любого другого неабстрактного свойства, не являющегося внешним, accessor_body является блоком , указывающим операторы, которые должны выполняться при вызове соответствующего метода доступа.For the accessors of any other non-abstract, non-extern property, the accessor_body is a block which specifies the statements to be executed when the corresponding accessor is invoked.

get метода доступа соответствует методу без параметров с возвращаемым значением типа свойства.A get accessor corresponds to a parameterless method with a return value of the property type. Если в выражении есть ссылка на свойство, кроме целевого объекта присваивания, то для расчета значения свойства (значения выражений) вызывается метод доступа get свойства.Except as the target of an assignment, when a property is referenced in an expression, the get accessor of the property is invoked to compute the value of the property (Values of expressions). Текст метода доступа get должен соответствовать правилам для методов, возвращающих значение, описанных в тексте метода.The body of a get accessor must conform to the rules for value-returning methods described in Method body. В частности, все операторы return в теле метода доступа get должны указывать выражение, которое неявно преобразуется в тип свойства.In particular, all return statements in the body of a get accessor must specify an expression that is implicitly convertible to the property type. Более того, конечная точка метода доступа get не должна быть достижима.Furthermore, the endpoint of a get accessor must not be reachable.

setный метод доступа соответствует методу с одним параметром-значением типа свойства и типом возвращаемого значения void.A set accessor corresponds to a method with a single value parameter of the property type and a void return type. Неявный параметр метода доступа set всегда называется value.The implicit parameter of a set accessor is always named value. Если на свойство имеется ссылка как на целевой объект назначения (Операторы присваивания), или в качестве операнда ++ или -- (Постфиксные операторы инкремента и декремента, префиксные операторы инкремента и декремента) вызывается метод доступа set с аргументом (значение которого совпадает с правой стороны присваивания или операндом оператора ++ или --), который предоставляет новое значение (простое присваивание).When a property is referenced as the target of an assignment (Assignment operators), or as the operand of ++ or -- (Postfix increment and decrement operators, Prefix increment and decrement operators), the set accessor is invoked with an argument (whose value is that of the right-hand side of the assignment or the operand of the ++ or -- operator) that provides the new value (Simple assignment). Тело метода доступа set должно соответствовать правилам для void методов, описанных в тексте метода.The body of a set accessor must conform to the rules for void methods described in Method body. В частности, инструкции return в теле метода доступа set не могут указывать выражение.In particular, return statements in the set accessor body are not permitted to specify an expression. Так как метод доступа set неявно имеет параметр с именем value, это может быть ошибка времени компиляции для объявления локальной переменной или константы в методе доступа set для этого имени.Since a set accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declaration in a set accessor to have that name.

В зависимости от наличия или отсутствия методов доступа get и set свойство классифицируется следующим образом:Based on the presence or absence of the get and set accessors, a property is classified as follows:

  • Свойство, включающее метод доступа get и метод доступа set, называется свойством, предназначенным для чтения и записи .A property that includes both a get accessor and a set accessor is said to be a read-write property.
  • Свойство, имеющее только метод доступа get, называется свойством только для чтения .A property that has only a get accessor is said to be a read-only property. Это ошибка времени компиляции, когда свойство, доступное только для чтения, является целью назначения.It is a compile-time error for a read-only property to be the target of an assignment.
  • Свойство, имеющее только метод доступа set, называется свойством только для записи .A property that has only a set accessor is said to be a write-only property. За исключением целей назначения, для ссылки на свойство, доступное только для записи в выражении, возникает ошибка времени компиляции.Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression.

в примереIn the example

public class Button: Control
{
    private string caption;

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

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

элемент управления Button объявляет открытое свойство Caption.the Button control declares a public Caption property. Метод доступа get свойства Caption возвращает строку, хранящуюся в поле Private caption.The get accessor of the Caption property returns the string stored in the private caption field. Метод доступа set проверяет, отличается ли новое значение от текущего, и если да, то он сохраняет новое значение и перерисовывает элемент управления.The set accessor checks if the new value is different from the current value, and if so, it stores the new value and repaints the control. Свойства часто соответствуют приведенному выше шаблону: метод доступа get просто возвращает значение, хранящееся в частном поле, а метод доступа set изменяет это закрытое поле, а затем выполняет дополнительные действия, необходимые для полного обновления состояния объекта.Properties often follow the pattern shown above: The get accessor simply returns a value stored in a private field, and the set accessor modifies that private field and then performs any additional actions required to fully update the state of the object.

В приведенном выше примере класса Button используется следующий пример использования свойства Caption.Given the Button class above, the following is an example of use of the Caption property:

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

Здесь метод доступа set вызывается путем присвоения значения свойству, а метод доступа get вызывается ссылкой на свойство в выражении.Here, the set accessor is invoked by assigning a value to the property, and the get accessor is invoked by referencing the property in an expression.

Методы доступа get и set свойства не являются отдельными членами, и невозможно объявить методы доступа свойства отдельно.The get and set accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. Таким образом, у двух методов доступа к свойству, доступном для чтения и записи, нет возможности иметь разную доступность.As such, it is not possible for the two accessors of a read-write property to have different accessibility. ПримерThe example

class A
{
    private string name;

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

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

не объявляет одно свойство для чтения и записи.does not declare a single read-write property. Вместо этого он объявляет два свойства с одинаковым именем: один только для чтения и один только для записи.Rather, it declares two properties with the same name, one read-only and one write-only. Поскольку два члена, объявленных в одном классе, не могут иметь одинаковые имена, в примере возникает ошибка времени компиляции.Since two members declared in the same class cannot have the same name, the example causes a compile-time error to occur.

Если производный класс объявляет свойство с тем же именем, что и унаследованное свойство, то производное свойство скрывает унаследованное свойство относительно чтения и записи.When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing. в примереIn the example

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

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

Свойство P в B скрывает свойство P в A в отношении чтения и записи.the P property in B hides the P property in A with respect to both reading and writing. Таким же оператором в инструкцияхThus, in the statements

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

Назначение b.P вызывает ошибку времени компиляции, так как свойство P только для чтения в B скрывает свойство P только для записи в A.the assignment to b.P causes a compile-time error to be reported, since the read-only P property in B hides the write-only P property in A. Однако обратите внимание, что приведение можно использовать для доступа к свойству Hidden P.Note, however, that a cast can be used to access the hidden P property.

В отличие от общедоступных полей, свойства обеспечивают разделение между внутренним состоянием объекта и его открытым интерфейсом.Unlike public fields, properties provide a separation between an object's internal state and its public interface. Рассмотрим пример.Consider the example:

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 {
        get { return x; }
    }

    public int Y {
        get { return y; }
    }

    public Point Location {
        get { return new Point(x, y); }
    }

    public string Caption {
        get { return caption; }
    }
}

Здесь класс Label использует два поля int, x и y, для хранения своего расположения.Here, the Label class uses two int fields, x and y, to store its location. Расположение общедоступно в качестве X, свойства Y и свойства Location типа Point.The location is publicly exposed both as an X and a Y property and as a Location property of type Point. Если в следующей версии Labelстановится удобнее хранить расположение как Point внутренним, это изменение можно выполнить, не влияя на открытый интерфейс класса:If, in a future version of Label, it becomes more convenient to store the location as a Point internally, the change can be made without affecting the public interface of the class:

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 {
        get { return location.x; }
    }

    public int Y {
        get { return location.y; }
    }

    public Point Location {
        get { return location; }
    }

    public string Caption {
        get { return caption; }
    }
}

Имели x и y public readonly поля, было бы невозможно внести такое изменение в класс Label.Had x and y instead been public readonly fields, it would have been impossible to make such a change to the Label class.

Предоставление состояния через свойства не обязательно является менее эффективным, чем предоставление полей напрямую.Exposing state through properties is not necessarily any less efficient than exposing fields directly. В частности, если свойство не является виртуальным и содержит только небольшой объем кода, среда выполнения может заменить вызовы методов доступа на фактический код методов доступа.In particular, when a property is non-virtual and contains only a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. Этот процесс известен как встраиваниеи делает доступ к свойству как эффективный для доступа к полям, тем самым сохраняя при этом повышенную гибкость свойств.This process is known as inlining, and it makes property access as efficient as field access, yet preserves the increased flexibility of properties.

Поскольку вызов метода доступа get концептуально эквивалентен считыванию значения поля, он считается неправильным стилем программирования для методов доступа get, чтобы они имели наблюдаемые побочные эффекты.Since invoking a get accessor is conceptually equivalent to reading the value of a field, it is considered bad programming style for get accessors to have observable side-effects. в примереIn the example

class Counter
{
    private int next;

    public int Next {
        get { return next++; }
    }
}

значение свойства Next зависит от числа ранее выполненных операций доступа к этому свойству.the value of the Next property depends on the number of times the property has previously been accessed. Таким образом, при доступе к свойству создается наблюдаемый побочный результат, и свойство должно быть реализовано как метод.Thus, accessing the property produces an observable side-effect, and the property should be implemented as a method instead.

Соглашение "без побочных эффектов" для методов доступа get не означает, что get методы доступа всегда должны быть написаны так, чтобы они просто возвращали значения, хранящиеся в полях.The "no side-effects" convention for get accessors doesn't mean that get accessors should always be written to simply return values stored in fields. Действительно, методы доступа get часто вычисляют значение свойства, обращаясь к нескольким полям или вызывая методы.Indeed, get accessors often compute the value of a property by accessing multiple fields or invoking methods. Однако правильно спроектированный метод доступа get не выполняет никаких действий, которые приводят к наблюдаемым изменениям в состоянии объекта.However, a properly designed get accessor performs no actions that cause observable changes in the state of the object.

Свойства можно использовать для задержки инициализации ресурса до момента первого обращения к нему.Properties can be used to delay initialization of a resource until the moment it is first referenced. Например:For example:

using System.IO;

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;
        }
    }
}

Класс Console содержит три свойства: In, Outи Error, которые представляют стандартные устройства ввода, вывода и ошибки соответственно.The Console class contains three properties, In, Out, and Error, that represent the standard input, output, and error devices, respectively. Предоставляя эти члены как свойства, класс Console может задержать их инициализацию, пока они не будут фактически использованы.By exposing these members as properties, the Console class can delay their initialization until they are actually used. Например, при первом обращении к свойству Out, как вFor example, upon first referencing the Out property, as in

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

будет создан базовый TextWriter для выходного устройства.the underlying TextWriter for the output device is created. Но если приложение не делает ссылку на In и Error свойства, для этих устройств объекты не создаются.But if the application makes no reference to the In and Error properties, then no objects are created for those devices.

Автоматически реализованные свойстваAutomatically implemented properties

Автоматически реализуемое свойство (или Автоматическое свойство ) является неабстрактным свойством, не являющимся внешним, с использованием только точек с запятой.An automatically implemented property (or auto-property for short), is a non-abstract non-extern property with semicolon-only accessor bodies. Автоматические свойства должны иметь метод доступа get и при необходимости иметь метод доступа set.Auto-properties must have a get accessor and can optionally have a set accessor.

Если свойство задано как автоматически реализуемое свойство, скрытое резервное поле автоматически доступно для свойства, а методы доступа реализуются для чтения и записи в это резервное поле.When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field. Если автоматическое свойство не имеет метода доступа set, то резервное поле считается readonly (поля только для чтения).If the auto-property has no set accessor, the backing field is considered readonly (Readonly fields). Как и в случае с полем readonly, в теле конструктора включающего класса также может быть назначено автоматическое свойство только для получения.Just like a readonly field, a getter-only auto-property can also be assigned to in the body of a constructor of the enclosing class. Такое присваивание присваивается непосредственно полю резервного резервирования свойства.Such an assignment assigns directly to the readonly backing field of the property.

Автоматическое свойство может дополнительно иметь property_initializer, который применяется непосредственно к резервному полю как variable_initializer (Инициализаторы переменных).An auto-property may optionally have a property_initializer, which is applied directly to the backing field as a variable_initializer (Variable initializers).

В следующем примере происходит следующее:The following example:

public class Point {
    public int X { get; set; } = 0;
    public int Y { get; set; } = 0;
}

эквивалентно следующему объявлению:is equivalent to the following declaration:

public class Point {
    private int __x = 0;
    private int __y = 0;
    public int X { get { return __x; } set { __x = value; } }
    public int Y { get { return __y; } set { __y = value; } }
}

В следующем примере происходит следующее:The following example:

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

эквивалентно следующему объявлению:is equivalent to the following declaration:

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; }
}

Обратите внимание, что присваивания для поля readonly являются допустимыми, так как они встречаются в конструкторе.Notice that the assignments to the readonly field are legal, because they occur within the constructor.

Специальные возможностиAccessibility

Если метод доступа имеет accessor_modifier, то домен доступности (Домены доступности) метода доступа определяется с помощью объявленной доступности accessor_modifier.If an accessor has an accessor_modifier, the accessibility domain (Accessibility domains) of the accessor is determined using the declared accessibility of the accessor_modifier. Если у метода доступа нет accessor_modifier, домен доступности метода доступа определяется на основе объявленной доступности свойства или индексатора.If an accessor does not have an accessor_modifier, the accessibility domain of the accessor is determined from the declared accessibility of the property or indexer.

Присутствие accessor_modifier не влияет на поиск члена (Операторы) или разрешение перегрузки (разрешение перегрузки).The presence of an accessor_modifier never affects member lookup (Operators) or overload resolution (Overload resolution). Модификаторы свойства или индексатора всегда определяют, к какому свойству или индексатору привязан объект, независимо от контекста доступа.The modifiers on the property or indexer always determine which property or indexer is bound to, regardless of the context of the access.

После выбора конкретного свойства или индексатора используются домены специальных методов доступа, чтобы определить, является ли это использование допустимым.Once a particular property or indexer has been selected, the accessibility domains of the specific accessors involved are used to determine if that usage is valid:

  • Если использование является значением (значениями выражений), то метод доступа get должен существовать и быть доступным.If the usage is as a value (Values of expressions), the get accessor must exist and be accessible.
  • Если использование является целевым объектом простого назначения (простое присваивание), то метод доступа set должен существовать и быть доступным.If the usage is as the target of a simple assignment (Simple assignment), the set accessor must exist and be accessible.
  • Если использование является целевым объектом составного присваивания (составное присваивание) или в качестве целевого объекта для операторов ++ или -- (функции-члены.9, выражения вызова), то оба метода доступа get и метод доступа set должны существовать и быть доступными.If the usage is as the target of compound assignment (Compound assignment), or as the target of the ++ or -- operators (Function members.9, Invocation expressions), both the get accessors and the set accessor must exist and be accessible.

В следующем примере свойство A.Text скрыто свойством B.Text, даже в контекстах, где вызывается только метод доступа set.In the following example, the property A.Text is hidden by the property B.Text, even in contexts where only the set accessor is called. Напротив, свойство B.Count недоступно Mкласса, поэтому вместо него используется доступное свойство A.Count.In contrast, the property B.Count is not accessible to class M, so the accessible property A.Count is used instead.

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

    public int Count {
        get { return 5; }
        set { }
    }
}

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

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

    new protected int Count { 
        get { return 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
    }
}

Метод доступа, используемый для реализации интерфейса, может не иметь accessor_modifier.An accessor that is used to implement an interface may not have an accessor_modifier. Если для реализации интерфейса используется только один метод доступа, другой метод доступа может быть объявлен с accessor_modifier:If only one accessor is used to implement an interface, the other accessor may be declared with an accessor_modifier:

public interface I
{
    string Prop { get; }
}

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

Виртуальные, запечатанные, переопределяемые и абстрактные методы доступа к свойствамVirtual, sealed, override, and abstract property accessors

Объявление свойства virtual указывает, что методы доступа свойства являются виртуальными.A virtual property declaration specifies that the accessors of the property are virtual. Модификатор virtual применяется к обоим методам доступа для свойства, доступного для чтения и записи, поэтому невозможно использовать Virtual в качестве единственного метода доступа для свойства, предназначенного для чтения и записи.The virtual modifier applies to both accessors of a read-write property—it is not possible for only one accessor of a read-write property to be virtual.

Объявление свойства abstract указывает, что методы доступа свойства являются виртуальными, но не предоставляют фактическую реализацию методов доступа.An abstract property declaration specifies that the accessors of the property are virtual, but does not provide an actual implementation of the accessors. Вместо этого неабстрактные производные классы должны предоставлять собственную реализацию для методов доступа, переопределяя свойство.Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the property. Поскольку метод доступа для объявления абстрактного свойства не предоставляет фактической реализации, его accessor_body просто состоит из точки с запятой.Because an accessor for an abstract property declaration provides no actual implementation, its accessor_body simply consists of a semicolon.

Объявление свойства, включающее модификаторы abstract и override, указывает, что свойство является абстрактным и переопределяет базовое свойство.A property declaration that includes both the abstract and override modifiers specifies that the property is abstract and overrides a base property. Методы доступа такого свойства также являются абстрактными.The accessors of such a property are also abstract.

Объявления абстрактных свойств разрешены только в абстрактных классах (абстрактные классы). Методы доступа унаследованного виртуального свойства могут быть переопределены в производном классе путем включения объявления свойства, определяющего директиву override.Abstract property declarations are only permitted in abstract classes (Abstract classes).The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an override directive. Это называется объявлением свойства переопределения.This is known as an overriding property declaration. Переопределяющее объявление свойства не объявляет новое свойство.An overriding property declaration does not declare a new property. Вместо этого он просто специализирует реализации методов доступа к существующему виртуальному свойству.Instead, it simply specializes the implementations of the accessors of an existing virtual property.

В объявлении переопределяющего свойства должны быть указаны те же модификаторы доступа, тип и имя, что и у унаследованного свойства.An overriding property declaration must specify the exact same accessibility modifiers, type, and name as the inherited property. Если унаследованное свойство имеет только один метод доступа (т. е. Если унаследованное свойство доступно только для чтения или только для записи), переопределяющее свойство должно включать только этот метод доступа.If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property must include only that accessor. Если унаследованное свойство содержит оба метода доступа (т. е. Если унаследованное свойство доступно для чтения и записи), переопределяющее свойство может включать один метод доступа или оба.If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.

Переопределяющее объявление свойства может включать модификатор sealed.An overriding property declaration may include the sealed modifier. Использование этого модификатора предотвращает дальнейшее переопределение свойства в производном классе.Use of this modifier prevents a derived class from further overriding the property. Методы доступа запечатанного свойства также запечатаны.The accessors of a sealed property are also sealed.

За исключением различий в синтаксисе объявления и вызова, виртуальные, запечатанные, переопределяемые и абстрактные методы доступа ведут себя точно так же, как виртуальные, запечатанные, переопределяемые и абстрактные способы.Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. В частности, правила, описанные в статье виртуальные методы, методы переопределения, Запечатанные методыи абстрактные методы , применяются, как если бы методы доступа были методами соответствующей формы:Specifically, the rules described in Virtual methods, Override methods, Sealed methods, and Abstract methods apply as if accessors were methods of a corresponding form:

  • getный метод доступа соответствует методу без параметров с возвращаемым значением типа свойства и тем же модификаторам, что и содержащее свойство.A get accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property.
  • set метода доступа соответствует методу с одним параметром значения типа свойства, void возвращаемого типа и теми же модификаторами, что и у содержащего его свойства.A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.

в примереIn the example

abstract class A
{
    int y;

    public virtual int X {
        get { return 0; }
    }

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

    public abstract int Z { get; set; }
}

X является виртуальным свойством только для чтения, Y является виртуальным свойством для чтения и записи, а Z является абстрактным свойством для чтения и записи.X is a virtual read-only property, Y is a virtual read-write property, and Z is an abstract read-write property. Поскольку Z является абстрактным, содержащий класс A также должен быть объявлен абстрактным.Because Z is abstract, the containing class A must also be declared abstract.

Класс, производный от A, показан ниже:A class that derives from A is show below:

class B: A
{
    int z;

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

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

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

Здесь объявления X, Yи Z переопределяют объявления свойств.Here, the declarations of X, Y, and Z are overriding property declarations. Каждое объявление свойства точно соответствует модификаторам доступа, типу и имени соответствующего наследуемого свойства.Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. Метод доступа get X и set метода доступа Y используют ключевое слово base для доступа к унаследованным методам доступа.The get accessor of X and the set accessor of Y use the base keyword to access the inherited accessors. Объявление Z переопределяет как абстрактные методы доступа, так и не содержит необработанных абстрактных членов функций в B, а B может быть не абстрактным классом.The declaration of Z overrides both abstract accessors—thus, there are no outstanding abstract function members in B, and B is permitted to be a non-abstract class.

Если свойство объявлено как override, все переопределенные методы доступа должны быть доступны для переопределения кода.When a property is declared as an override, any overridden accessors must be accessible to the overriding code. Кроме того, объявленный доступ как для свойства, так и для индексатора, и для методов доступа должен совпадать с модификатором переопределенного члена и методов доступа.In addition, the declared accessibility of both the property or indexer itself, and of the accessors, must match that of the overridden member and accessors. Например:For example:

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

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

СобытияEvents

Событие — это элемент, который позволяет объекту или классу предоставлять уведомления.An event is a member that enables an object or class to provide notifications. Клиенты могут вкладывать исполняемый код для событий, предоставляя обработчики событий.Clients can attach executable code for events by supplying event handlers.

События объявляются с помощью event_declarations:Events are declared using event_declarations:

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'
    | event_modifier_unsafe
    ;

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
    ;

Event_declaration может включать набор атрибутов (атрибутов) и допустимое сочетание четырех модификаторов доступа (модификаторы доступа), new (Модификатор new), static (статический метод и методы экземпляра), virtual (виртуальные методы), override (методы переопределения), sealed (Запечатанные методы), abstract (абстрактные методы) и модификаторы extern (Внешние методы).An event_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.

Объявления событий подчиняются тем же правилам, что и объявления методов (методы) в отношении допустимых сочетаний модификаторов.Event declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers.

Тип объявления события должен быть delegate_type (Ссылочные типы), и этот delegate_type должен иметь по крайней мере такой же уровень доступности, как и само событие (ограничения доступности).The type of an event declaration must be a delegate_type (Reference types), and that delegate_type must be at least as accessible as the event itself (Accessibility constraints).

Объявление события может включать event_accessor_declarations.An event declaration may include event_accessor_declarations. Однако если это не так, то в случае невнешнего неабстрактного события компилятор предоставляет их автоматически (события, подобные полям); для внешних событий методы доступа предоставляются извне.However, if it does not, for non-extern, non-abstract events, the compiler supplies them automatically (Field-like events); for extern events, the accessors are provided externally.

Объявление события, которое опускает event_accessor_declarations определяет одно или несколько событий — по одному для каждого из variable_declarator.An event declaration that omits event_accessor_declarations defines one or more events—one for each of the variable_declarators. Атрибуты и модификаторы применяются ко всем членам, объявленным такими event_declaration.The attributes and modifiers apply to all of the members declared by such an event_declaration.

При event_declaration для включения модификатора abstract и event_accessor_declarationsс разделителями-скобками используется ошибка времени компиляции.It is a compile-time error for an event_declaration to include both the abstract modifier and brace-delimited event_accessor_declarations.

Если объявление события включает модификатор extern, то событие называется внешним событием.When an event declaration includes an extern modifier, the event is said to be an external event. Поскольку объявление внешнего события не предоставляет фактической реализации, оно является ошибкой для включения модификатора extern и event_accessor_declarations.Because an external event declaration provides no actual implementation, it is an error for it to include both the extern modifier and event_accessor_declarations.

Это ошибка времени компиляции для variable_declarator объявления события с модификатором abstract или external для включения variable_initializer.It is a compile-time error for a variable_declarator of an event declaration with an abstract or external modifier to include a variable_initializer.

Событие может использоваться в качестве левого операнда операторов += и -= (назначение события).An event can be used as the left-hand operand of the += and -= operators (Event assignment). Эти операторы используются соответственно, чтобы присоединить обработчики событий к или удалить обработчики событий из события, а модификаторы доступа события управляют контекстами, в которых разрешены такие операции.These operators are used, respectively, to attach event handlers to or to remove event handlers from an event, and the access modifiers of the event control the contexts in which such operations are permitted.

Поскольку += и -= являются единственными операциями, которые разрешены для события вне типа, объявляющего событие, внешний код может добавлять и удалять обработчики для события, но не может каким либо иным образом получать или изменять базовый список обработчиков событий.Since += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

В операции с формой x += y или x -= y, когда x является событием и ссылка выполняется вне типа, содержащего объявление x, результат операции имеет тип void (в отличие от типа x, со значением x после присваивания).In an operation of the form x += y or x -= y, when x is an event and the reference takes place outside the type that contains the declaration of x, the result of the operation has type void (as opposed to having the type of x, with the value of x after the assignment). Это правило запрещает внешнему коду косвенный анализ базового делегата события.This rule prohibits external code from indirectly examining the underlying delegate of an event.

В следующем примере показано, как обработчики событий присоединяются к экземплярам класса Button:The following example shows how event handlers are attached to instances of the Button class:

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
    }
}

Здесь конструктор LoginDialog экземпляра создает два экземпляра Button и присоединяет обработчики событий к событиям Click.Here, the LoginDialog instance constructor creates two Button instances and attaches event handlers to the Click events.

События, подобные полямField-like events

В тексте программы класса или структуры, содержащей объявление события, можно использовать определенные события, такие как поля.Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. Для использования таким образом событие не должно быть abstract или externи не должно явно включать event_accessor_declarations.To be used in this way, an event must not be abstract or extern, and must not explicitly include event_accessor_declarations. Такое событие может использоваться в любом контексте, допускающем использование поля.Such an event can be used in any context that permits a field. Поле содержит делегат (делегаты), который ссылается на список обработчиков событий, добавленных к событию.The field contains a delegate (Delegates) which refers to the list of event handlers that have been added to the event. Если обработчики событий не были добавлены, поле содержит null.If no event handlers have been added, the field contains null.

в примереIn the example

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

public class Button: Control
{
    public event EventHandler Click;

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

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

Click используется в качестве поля в классе Button.Click is used as a field within the Button class. Как показано в примере, поле может быть проверено, изменено и использовано в выражениях вызова делегата.As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. Метод OnClick в классе Button "вызывает событие Click.The OnClick method in the Button class "raises" the Click event. Концепция создания события в точности соответствует вызову делегата, представленного этим событием. Это позволяет обойтись без особой языковой конструкции для создания событий.The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events. Обратите внимание, что вызову делегата предшествует проверка, которая гарантирует, что делегат не имеет значение null.Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.

За пределами объявления класса Button элемент Click можно использовать только в левой части операторов += и -=, как вOutside the declaration of the Button class, the Click member can only be used on the left-hand side of the += and -= operators, as in

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

При этом делегат добавляется в список вызовов события Click.which appends a delegate to the invocation list of the Click event, and

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

который удаляет делегат из списка вызовов события Click.which removes a delegate from the invocation list of the Click event.

При компиляции события, похожего на поле, компилятор автоматически создает хранилище для хранения делегата и создает методы доступа для события, которое добавляет или удаляет обработчики событий в поле делегата.When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field. Операции сложения и удаления являются потокобезопасными и могут (но не обязательно) выполняться при удержании блокировки (инструкции lock) на содержащем его объекте для события экземпляра или объекта типа (выражения создания анонимных объектов) для статического события.The addition and removal operations are thread safe, and may (but are not required to) be done while holding the lock (The lock statement) on the containing object for an instance event, or the type object (Anonymous object creation expressions) for a static event.

Таким образом, объявление события экземпляра в виде:Thus, an instance event declaration of the form:

class X
{
    public event D Ev;
}

будет скомпилировано в нечто эквивалентное:will be compiled to something equivalent to:

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 */
        }
    }
}

В Xкласса ссылки на Ev в левой части операторов += и -= вызывают вызов методов доступа Add и Remove.Within the class X, references to Ev on the left-hand side of the += and -= operators cause the add and remove accessors to be invoked. Все остальные ссылки на Ev компилируются для ссылки на скрытое поле __Ev вместо этого (доступ к членам).All other references to Ev are compiled to reference the hidden field __Ev instead (Member access). Имя «__Ev» произвольно; скрытое поле может иметь любое имя или вообще не иметь имени.The name "__Ev" is arbitrary; the hidden field could have any name or no name at all.

Методы доступа событийEvent accessors

Объявления событий обычно опускаются event_accessor_declarations, как показано в приведенном выше примере Button.Event declarations typically omit event_accessor_declarations, as in the Button example above. Одна из ситуаций для этого заключается в том, что стоимость хранения одного поля на событие не является приемлемой.One situation for doing so involves the case in which the storage cost of one field per event is not acceptable. В таких случаях класс может включать event_accessor_declarations и использовать частный механизм для хранения списка обработчиков событий.In such cases, a class can include event_accessor_declarations and use a private mechanism for storing the list of event handlers.

Event_accessor_declarations события указывают исполняемые операторы, связанные с добавлением и удалением обработчиков событий.The event_accessor_declarations of an event specify the executable statements associated with adding and removing event handlers.

Объявления метода доступа состоят из add_accessor_declaration и remove_accessor_declaration.The accessor declarations consist of an add_accessor_declaration and a remove_accessor_declaration. Каждое объявление метода доступа состоит из маркера add или remove, за которым следует блок.Each accessor declaration consists of the token add or remove followed by a block. Блок , связанный с add_accessor_declaration , задает инструкции, выполняемые при добавлении обработчика событий, и блок , связанный с remove_accessor_declaration , указывает инструкции, выполняемые при удалении обработчика событий.The block associated with an add_accessor_declaration specifies the statements to execute when an event handler is added, and the block associated with a remove_accessor_declaration specifies the statements to execute when an event handler is removed.

Каждый add_accessor_declaration и remove_accessor_declaration соответствует методу с одним параметром-значением типа события и типом возвращаемого значения void.Each add_accessor_declaration and remove_accessor_declaration corresponds to a method with a single value parameter of the event type and a void return type. Неявный параметр метода доступа к событию называется value.The implicit parameter of an event accessor is named value. Если событие используется в назначении события, используется соответствующий метод доступа к событию.When an event is used in an event assignment, the appropriate event accessor is used. В частности, если оператор присваивания имеет значение += то используется метод доступа Add, а если оператор присваивания -=, то используется метод доступа remove.Specifically, if the assignment operator is += then the add accessor is used, and if the assignment operator is -= then the remove accessor is used. В любом случае правый операнд оператора присваивания используется в качестве аргумента для метода доступа к событию.In either case, the right-hand operand of the assignment operator is used as the argument to the event accessor. Блок add_accessor_declaration или remove_accessor_declaration должен соответствовать правилам для void методов, описанных в тексте метода.The block of an add_accessor_declaration or a remove_accessor_declaration must conform to the rules for void methods described in Method body. В частности, операторам return в таком блоке не разрешается указывать выражение.In particular, return statements in such a block are not permitted to specify an expression.

Поскольку метод доступа к событию неявно имеет параметр с именем value, это может быть ошибка времени компиляции для локальной переменной или константы, объявленной в методе доступа к событию, чтобы иметь это имя.Since an event accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declared in an event accessor to have that name.

в примереIn the example

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);
    }
}

класс Control реализует внутренний механизм хранения для событий.the Control class implements an internal storage mechanism for events. Метод AddEventHandler связывает значение делегата с ключом, метод GetEventHandler возвращает делегат, который в настоящее время связан с ключом, а метод RemoveEventHandler удаляет делегат в качестве обработчика событий для указанного события.The AddEventHandler method associates a delegate value with a key, the GetEventHandler method returns the delegate currently associated with a key, and the RemoveEventHandler method removes a delegate as an event handler for the specified event. Предположительно, базовый механизм хранения разработан таким образом, что затраты на связывание значения делегата null с ключом не изменяются, поэтому необработанные события не используют хранилище.Presumably, the underlying storage mechanism is designed such that there is no cost for associating a null delegate value with a key, and thus unhandled events consume no storage.

Статические и события экземпляровStatic and instance events

Если объявление события включает модификатор static, то событие называется статическим событием.When an event declaration includes a static modifier, the event is said to be a static event. Если модификатор static отсутствует, событие называется событием экземпляра.When no static modifier is present, the event is said to be an instance event.

Статическое событие не связано с конкретным экземпляром и является ошибкой времени компиляции для ссылки на this в методы доступа статического события.A static event is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static event.

Событие экземпляра связано с данным экземпляром класса, и доступ к этому экземпляру можно получить как this (этот доступ) в подклассах доступа этого события.An instance event is associated with a given instance of a class, and this instance can be accessed as this (This access) in the accessors of that event.

Если на событие имеется ссылка в member_access (доступ к члену) в форме E.M, если M является статическим событием, E должен обменяться типом, содержащим M, а если M является событием экземпляра, E должен обозначить экземпляр типа, содержащего M.When an event is referenced in a member_access (Member access) of the form E.M, if M is a static event, E must denote a type containing M, and if M is an instance event, E must denote an instance of a type containing M.

Различия между статическими и членами экземпляра обсуждаются далее в статических членах и экземплярах.The differences between static and instance members are discussed further in Static and instance members.

Виртуальные, запечатанные, переопределяемые и абстрактные методы доступа к событиямVirtual, sealed, override, and abstract event accessors

virtual объявление события указывает, что методы доступа этого события являются виртуальными.A virtual event declaration specifies that the accessors of that event are virtual. Модификатор virtual применяется к обоим классам доступа события.The virtual modifier applies to both accessors of an event.

abstract объявление события указывает, что методы доступа события являются виртуальными, но не предоставляют фактическую реализацию методов доступа.An abstract event declaration specifies that the accessors of the event are virtual, but does not provide an actual implementation of the accessors. Вместо этого неабстрактные производные классы должны предоставлять собственную реализацию для методов доступа, переопределяя событие.Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the event. Поскольку объявление абстрактного события не предоставляет фактической реализации, оно не может предоставлять event_accessor_declarationsс разделителями-скобками.Because an abstract event declaration provides no actual implementation, it cannot provide brace-delimited event_accessor_declarations.

Объявление события, включающее модификаторы abstract и override, указывает, что событие является абстрактным и переопределяет базовое событие.An event declaration that includes both the abstract and override modifiers specifies that the event is abstract and overrides a base event. Методы доступа такого события также являются абстрактными.The accessors of such an event are also abstract.

Объявления абстрактных событий разрешены только в абстрактных классах (абстрактные классы).Abstract event declarations are only permitted in abstract classes (Abstract classes).

Методы доступа наследуемого виртуального события можно переопределить в производном классе, включив объявление события, которое указывает модификатор override.The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override modifier. Это называется объявлением переопределяющего события.This is known as an overriding event declaration. Объявление переопределяющего события не объявляет новое событие.An overriding event declaration does not declare a new event. Вместо этого он просто специализирует реализации методов доступа к существующему виртуальному событию.Instead, it simply specializes the implementations of the accessors of an existing virtual event.

В объявлении переопределяющего события должны быть указаны те же модификаторы доступности, тип и имя, что и у переопределенного события.An overriding event declaration must specify the exact same accessibility modifiers, type, and name as the overridden event.

Объявление переопределяющего события может включать модификатор sealed.An overriding event declaration may include the sealed modifier. Использование этого модификатора предотвращает дальнейшее переопределение события в производном классе.Use of this modifier prevents a derived class from further overriding the event. Методы доступа запечатанного события также запечатаны.The accessors of a sealed event are also sealed.

Ошибка времени компиляции при переопределении объявления события для включения модификатора new.It is a compile-time error for an overriding event declaration to include a new modifier.

За исключением различий в синтаксисе объявления и вызова, виртуальные, запечатанные, переопределяемые и абстрактные методы доступа ведут себя точно так же, как виртуальные, запечатанные, переопределяемые и абстрактные способы.Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. В частности, правила, описанные в статье виртуальные методы, методы переопределения, Запечатанные методыи абстрактные методы , действуют так, как если бы методы доступа были методами соответствующей формы.Specifically, the rules described in Virtual methods, Override methods, Sealed methods, and Abstract methods apply as if accessors were methods of a corresponding form. Каждый метод доступа соответствует методу с одним параметром-значением типа события, типом возвращаемого значения void и теми же модификаторами, что и у содержащего его события.Each accessor corresponds to a method with a single value parameter of the event type, a void return type, and the same modifiers as the containing event.

ИндексаторыIndexers

Индексатор — это член, который позволяет индексировать объект таким же образом, как и массив.An indexer is a member that enables an object to be indexed in the same way as an array. Индексаторы объявляются с помощью indexer_declarations:Indexers are declared using indexer_declarations:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | indexer_modifier_unsafe
    ;

indexer_declarator
    : type 'this' '[' formal_parameter_list ']'
    | type interface_type '.' 'this' '[' formal_parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;

Indexer_declaration может включать набор атрибутов (атрибутов) и допустимое сочетание четырех модификаторов доступа (модификаторы доступа), new (Модификатор new), virtual (виртуальные методы), override (методы переопределения), sealed (запечатанныеметоды), abstract (абстрактные методы), а также модификаторы extern (Внешние методы).An indexer_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.

Объявления индексатора подчиняются тем же правилам, что и объявления методов (методы) в отношении допустимых сочетаний модификаторов, за одним исключением является то, что модификатор static не разрешен в объявлении индексатора.Indexer declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers, with the one exception being that the static modifier is not permitted on an indexer declaration.

Модификаторы virtual, overrideи abstract являются взаимоисключающими, за исключением одного случая.The modifiers virtual, override, and abstract are mutually exclusive except in one case. Модификаторы abstract и override можно использовать вместе, чтобы абстрактный индексатор мог переопределять виртуальный.The abstract and override modifiers may be used together so that an abstract indexer can override a virtual one.

Тип объявления индексатора указывает тип элемента индексатора, представленного объявлением.The type of an indexer declaration specifies the element type of the indexer introduced by the declaration. Если индексатор не является явной реализацией члена интерфейса, за типом следует ключевое слово this.Unless the indexer is an explicit interface member implementation, the type is followed by the keyword this. Для явной реализации члена интерфейса за типом следует interface_type,.и ключевое слово this.For an explicit interface member implementation, the type is followed by an interface_type, a ".", and the keyword this. В отличие от других элементов, индексаторы не имеют определяемых пользователем имен.Unlike other members, indexers do not have user-defined names.

Formal_parameter_list задает параметры индексатора.The formal_parameter_list specifies the parameters of the indexer. Список формальных параметров индексатора соответствует методу метода (Параметры метода), за исключением того, что необходимо указать хотя бы один параметр и не допускаются модификаторы параметров ref и out.The formal parameter list of an indexer corresponds to that of a method (Method parameters), except that at least one parameter must be specified, and that the ref and out parameter modifiers are not permitted.

Тип индексатора и каждого из типов, на которые имеются ссылки в formal_parameter_list , должны иметь по крайней мере такой же уровень доступности, как и сам индексатор (ограничения доступности).The type of an indexer and each of the types referenced in the formal_parameter_list must be at least as accessible as the indexer itself (Accessibility constraints).

Indexer_body может состоять из тела метода доступа или тела выражения.An indexer_body may either consist of an accessor body or an expression body. В теле метода доступа accessor_declarations, который должен быть заключен в маркеры "{" и "}", объявите методы доступа (методы доступа) свойства.In an accessor body, accessor_declarations, which must be enclosed in "{" and "}" tokens, declare the accessors (Accessors) of the property. Методы доступа указывают исполняемые операторы, связанные с чтением и записью свойства.The accessors specify the executable statements associated with reading and writing the property.

Тело выражения, состоящее из "=>", за которым следует выражение E и точка с запятой точно эквивалентна тексту оператора { get { return E; } }, и поэтому может использоваться только для указания индексаторов только для получения, где результат получения данных предоставляется одним выражением.An expression body consisting of "=>" followed by an expression E and a semicolon is exactly equivalent to the statement body { get { return E; } }, and can therefore only be used to specify getter-only indexers where the result of the getter is given by a single expression.

Несмотря на то, что синтаксис для доступа к элементу индексатора такой же, как и для элемента массива, элемент индексатора не классифицируется как переменная.Even though the syntax for accessing an indexer element is the same as that for an array element, an indexer element is not classified as a variable. Таким образом, невозможно передать элемент индексатора как аргумент ref или out.Thus, it is not possible to pass an indexer element as a ref or out argument.

Список формальных параметров индексатора определяет сигнатуру (подписи и перегрузку) индексатора.The formal parameter list of an indexer defines the signature (Signatures and overloading) of the indexer. В частности, Сигнатура индексатора состоит из числа и типов его формальных параметров.Specifically, the signature of an indexer consists of the number and types of its formal parameters. Тип элемента и имена формальных параметров не являются частью сигнатуры индексатора.The element type and names of the formal parameters are not part of an indexer's signature.

Сигнатура индексатора должна отличаться от сигнатур всех других индексаторов, объявленных в том же классе.The signature of an indexer must differ from the signatures of all other indexers declared in the same class.

Индексаторы и свойства очень похожи в концепции, но отличаются следующими способами.Indexers and properties are very similar in concept, but differ in the following ways:

  • Свойство определяется по имени, а индексатор определяется по его сигнатуре.A property is identified by its name, whereas an indexer is identified by its signature.
  • Доступ к свойству осуществляется через simple_name (простые имена) или member_access (доступ к члену), а доступ к элементу индексатора осуществляется через element_access (доступ к индексатору).A property is accessed through a simple_name (Simple names) or a member_access (Member access), whereas an indexer element is accessed through an element_access (Indexer access).
  • Свойство может быть членом static, тогда как индексатор всегда является членом экземпляра.A property can be a static member, whereas an indexer is always an instance member.
  • get метод доступа к свойству соответствует методу без параметров, тогда как get метода доступа индексатора соответствует методу с тем же списком формальных параметров, что и у индексатора.A get accessor of a property corresponds to a method with no parameters, whereas a get accessor of an indexer corresponds to a method with the same formal parameter list as the indexer.
  • Метод доступа set свойства соответствует методу с одним параметром с именем value, тогда как метод доступа set индексатора соответствует методу с тем же списком формальных параметров, что и индексатор, а также дополнительный параметр с именем value.A set accessor of a property corresponds to a method with a single parameter named value, whereas a set accessor of an indexer corresponds to a method with the same formal parameter list as the indexer, plus an additional parameter named value.
  • Ошибка времени компиляции, когда метод доступа индексатора объявляет локальную переменную с тем же именем, что и параметр индексатора.It is a compile-time error for an indexer accessor to declare a local variable with the same name as an indexer parameter.
  • При объявлении переопределяющего свойства доступ к унаследованному свойству осуществляется с помощью синтаксиса base.P, где P является именем свойства.In an overriding property declaration, the inherited property is accessed using the syntax base.P, where P is the property name. При переопределении объявления индексатора доступ к унаследованному индексатору осуществляется с помощью синтаксиса base[E], где E является списком выражений, разделенных запятыми.In an overriding indexer declaration, the inherited indexer is accessed using the syntax base[E], where E is a comma separated list of expressions.
  • Понятие «автоматически реализованный индексатор» не существует.There is no concept of an "automatically implemented indexer". Наличие неабстрактного невнешнего индексатора с помощью методов доступа с точкой с запятой не является ошибкой.It is an error to have a non-abstract, non-external indexer with semicolon accessors.

Помимо этих различий, все правила, определенные в методыах доступа и автоматически реализуемые свойства , применяются к индексаторам доступа, а также к операторам доступа к свойствам.Aside from these differences, all rules defined in Accessors and Automatically implemented properties apply to indexer accessors as well as to property accessors.

Если объявление индексатора включает модификатор extern, то индексатор называется внешним индексатором.When an indexer declaration includes an extern modifier, the indexer is said to be an external indexer. Поскольку объявление внешнего индексатора не предоставляет фактической реализации, каждый из его accessor_declarations состоит из точки с запятой.Because an external indexer declaration provides no actual implementation, each of its accessor_declarations consists of a semicolon.

В приведенном ниже примере объявляется класс BitArray, реализующий индексатор для доступа к отдельным битам в массиве битов.The example below declares a BitArray class that implements an indexer for accessing the individual bits in the bit array.

using System;

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 {
        get { return 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);
            }
        }
    }
}

Экземпляр класса BitArray потребляет значительно меньше памяти, чем соответствующий bool[] (поскольку каждое значение первого из них занимает только один бит, а не один байт), но позволяет выполнять те же операции, что и bool[].An instance of the BitArray class consumes substantially less memory than a corresponding bool[] (since each value of the former occupies only one bit instead of the latter's one byte), but it permits the same operations as a bool[].

Следующий класс CountPrimes использует BitArray и классический алгоритм "Сиеве" для расчета количества простых чисел от 1 до заданного максимума:The following CountPrimes class uses a BitArray and the classical "sieve" algorithm to compute the number of primes between 1 and a given maximum:

class CountPrimes
{
    static int Count(int max) {
        BitArray flags = new BitArray(max + 1);
        int count = 1;
        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 {0} primes between 1 and {1}", count, max);
    }
}

Обратите внимание, что синтаксис для доступа к элементам BitArray точно такой же, как и для bool[].Note that the syntax for accessing elements of the BitArray is precisely the same as for a bool[].

В следующем примере показан класс таблицы 26 * 10 с индексатором с двумя параметрами.The following example shows a 26 * 10 grid class that has an indexer with two parameters. Первый параметр должен быть буквой верхнего или нижнего регистра в диапазоне A – Z, а второй должен быть целым числом в диапазоне 0-9.The first parameter is required to be an upper- or lowercase letter in the range A-Z, and the second is required to be an integer in the range 0-9.

using System;

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;

    int[,] cells = new int[NumRows, NumCols];

    public int this[char c, int col] {
        get {
            c = Char.ToUpper(c);
            if (c < 'A' || c > 'Z') {
                throw new ArgumentException();
            }
            if (col < 0 || col >= NumCols) {
                throw new IndexOutOfRangeException();
            }
            return cells[c - 'A', col];
        }

        set {
            c = Char.ToUpper(c);
            if (c < 'A' || c > 'Z') {
                throw new ArgumentException();
            }
            if (col < 0 || col >= NumCols) {
                throw new IndexOutOfRangeException();
            }
            cells[c - 'A', col] = value;
        }
    }
}

Перегрузка индексатораIndexer overloading

Правила разрешения перегрузки индексатора описаны в разделе Определение типа.The indexer overload resolution rules are described in Type inference.

ОператорыOperators

Оператор — это член, определяющий значение оператора выражения, которое может быть применено к экземплярам класса.An operator is a member that defines the meaning of an expression operator that can be applied to instances of the class. Операторы объявляются с помощью operator_declarations:Operators are declared using operator_declarations:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | operator_modifier_unsafe
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' type identifier ')'
    ;

overloadable_unary_operator
    : '+' | '-' | '!' | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator '(' type identifier ',' type identifier ')'
    ;

overloadable_binary_operator
    : '+'   | '-'   | '*'   | '/'   | '%'   | '&'   | '|'   | '^'   | '<<'
    | right_shift | '=='  | '!='  | '>'   | '<'   | '>='  | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' type identifier ')'
    | 'explicit' 'operator' type '(' type identifier ')'
    ;

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

Существует три категории перегруженных операторов: унарные операторы (Унарные операторы), бинарные операторы (бинарные операторы) и операторы преобразования (Операторы преобразования).There are three categories of overloadable operators: Unary operators (Unary operators), binary operators (Binary operators), and conversion operators (Conversion operators).

Operator_body является точкой с запятой, телом оператора или телом выражения.The operator_body is either a semicolon, a statement body or an expression body. Тело оператора состоит из блока, в котором указываются операторы, выполняемые при вызове оператора.A statement body consists of a block, which specifies the statements to execute when the operator is invoked. Блок должен соответствовать правилам для методов, возвращающих значение, описанных в тексте метода.The block must conform to the rules for value-returning methods described in Method body. Тело выражения состоит из =>, за которым следует выражение и точка с запятой, и обозначает одно выражение, выполняемое при вызове оператора.An expression body consists of => followed by an expression and a semicolon, and denotes a single expression to perform when the operator is invoked.

Для extern операторов operator_body состоит из точки с запятой.For extern operators, the operator_body consists simply of a semicolon. Для всех остальных операторов operator_body является либо телом блока, либо телом выражения.For all other operators, the operator_body is either a block body or an expression body.

Следующие правила применяются ко всем объявлениям операторов.The following rules apply to all operator declarations:

  • Объявление оператора должно включать модификаторы public и static.An operator declaration must include both a public and a static modifier.
  • Параметры оператора должны быть параметрами значений (Параметры значения).The parameter(s) of an operator must be value parameters (Value parameters). При объявлении оператора для указания ref или out параметров возникает ошибка времени компиляции.It is a compile-time error for an operator declaration to specify ref or out parameters.
  • Сигнатура оператора (Унарные операторы, бинарные операторы, Операторы преобразования) должна отличаться от сигнатур всех других операторов, объявленных в том же классе.The signature of an operator (Unary operators, Binary operators, Conversion operators) must differ from the signatures of all other operators declared in the same class.
  • Все типы, упоминаемые в объявлении оператора, должны иметь по крайней мере такой же уровень доступности, как и сам оператор (ограничения доступности).All types referenced in an operator declaration must be at least as accessible as the operator itself (Accessibility constraints).
  • Ошибка одного и того же модификатора встречается несколько раз в объявлении оператора.It is an error for the same modifier to appear multiple times in an operator declaration.

Каждая категория операторов накладывает дополнительные ограничения, как описано в следующих разделах.Each operator category imposes additional restrictions, as described in the following sections.

Как и другие члены, операторы, объявленные в базовом классе, наследуются производными классами.Like other members, operators declared in a base class are inherited by derived classes. Поскольку в объявлениях операторов всегда требуется класс или структура, в которых оператор объявляется для участия в сигнатуре оператора, то оператор, объявленный в производном классе, не может скрыть оператор, объявленный в базовом классе.Because operator declarations always require the class or struct in which the operator is declared to participate in the signature of the operator, it is not possible for an operator declared in a derived class to hide an operator declared in a base class. Таким образом, модификатор new не является обязательным и, следовательно, никогда не разрешается в объявлении оператора.Thus, the new modifier is never required, and therefore never permitted, in an operator declaration.

Дополнительные сведения о унарных и бинарных операторах можно найти в операторах.Additional information on unary and binary operators can be found in Operators.

Дополнительные сведения об операторах преобразования можно найти в пользовательских преобразованиях.Additional information on conversion operators can be found in User-defined conversions.

Унарные операторыUnary operators

Следующие правила применяются к объявлениям унарных операторов, где T обозначает тип экземпляра класса или структуры, содержащей объявление оператора:The following rules apply to unary operator declarations, where T denotes the instance type of the class or struct that contains the operator declaration:

  • Унарный +, -, !или ~ должен принимать один параметр типа T или T? и может возвращать любой тип.A unary +, -, !, or ~ operator must take a single parameter of type T or T? and can return any type.
  • Унарный ++ или оператор -- должен принимать один параметр типа T или T? и должен возвращать этот же тип или производный от него тип.A unary ++ or -- operator must take a single parameter of type T or T? and must return that same type or a type derived from it.
  • Унарный true или оператор false должен принимать один параметр типа T или T? и должен возвращать boolтипа.A unary true or false operator must take a single parameter of type T or T? and must return type bool.

Сигнатура унарного оператора состоит из токена оператора (+, -, !, ~, ++, --, trueили false) и типа одного формального параметра.The signature of a unary operator consists of the operator token (+, -, !, ~, ++, --, true, or false) and the type of the single formal parameter. Возвращаемый тип не является частью сигнатуры унарного оператора и не является именем формального параметра.The return type is not part of a unary operator's signature, nor is the name of the formal parameter.

Унарным операторам true и false требуется попарное объявление.The true and false unary operators require pair-wise declaration. Ошибка времени компиляции возникает, если класс объявляет один из этих операторов без объявления другого.A compile-time error occurs if a class declares one of these operators without also declaring the other. Операторы true и false описаны далее в пользовательских условных логических операторах и логических выражениях.The true and false operators are described further in User-defined conditional logical operators and Boolean expressions.

В следующем примере показана реализация и последующее использование operator ++ для класса целочисленного вектора:The following example shows an implementation and subsequent usage of operator ++ for an integer vector class:

public class IntVector
{
    public IntVector(int length) {...}

    public int Length {...}                 // read-only property

    public int this[int index] {...}        // 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
    }
}

Обратите внимание, что метод оператора возвращает значение, полученное путем добавления 1 к операнду, точно так же, как Постфиксные операторы инкремента и декремента (Постфиксные операторыинкремента и декремента), а также операторы инкремента и декремента префикса (Операторы инкремента и декремента).Note how the operator method returns the value produced by adding 1 to the operand, just like the postfix increment and decrement operators (Postfix increment and decrement operators), and the prefix increment and decrement operators (Prefix increment and decrement operators). В C++отличие от, этот метод не требует непосредственного изменения значения операнда.Unlike in C++, this method need not modify the value of its operand directly. Фактически изменение значения операнда нарушает стандартную семантику оператора постфиксного инкремента.In fact, modifying the operand value would violate the standard semantics of the postfix increment operator.

Бинарные операторыBinary operators

Следующие правила применяются к объявлениям бинарных операторов, где T обозначает тип экземпляра класса или структуры, содержащей объявление оператора:The following rules apply to binary operator declarations, where T denotes the instance type of the class or struct that contains the operator declaration:

  • Бинарный оператор, не являющийся сдвигом, должен принимать два параметра, по крайней мере один из которых должен иметь тип T или T?и может возвращать любой тип.A binary non-shift operator must take two parameters, at least one of which must have type T or T?, and can return any type.
  • Бинарный << или оператор >> должен принимать два параметра, первый из которых должен иметь тип T или T?, а второй из которых должен иметь тип int или int?и может возвращать любой тип.A binary << or >> operator must take two parameters, the first of which must have type T or T? and the second of which must have type int or int?, and can return any type.

Сигнатура бинарного оператора состоит из токена оператора (+, -, *, /, %, &, |, ^, <<, >>, ==или !=) и типов двух формальных параметров.>``<``>=``<=The signature of a binary operator consists of the operator token (+, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, or <=) and the types of the two formal parameters. Тип возвращаемого значения и имена формальных параметров не являются частью сигнатуры бинарного оператора.The return type and the names of the formal parameters are not part of a binary operator's signature.

Для определенных бинарных операторов требуется попарное объявление.Certain binary operators require pair-wise declaration. Для каждого объявления любого из операторов пары должно существовать совпадающее объявление другого оператора пары.For every declaration of either operator of a pair, there must be a matching declaration of the other operator of the pair. Два объявления оператора совпадают, если они имеют одинаковый тип возвращаемого значения и один и тот же тип для каждого параметра.Two operator declarations match when they have the same return type and the same type for each parameter. Следующие операторы должны требовать попарное объявление:The following operators require pair-wise declaration:

  • operator == и operator !=operator == and operator !=
  • operator > и operator <operator > and operator <
  • operator >= и operator <=operator >= and operator <=

Операторы преобразованияConversion operators

Объявление оператора преобразования вводит определяемое пользователем преобразование (определяемое пользователем преобразование), которое дополняет предварительно определенные явные и неявные преобразования.A conversion operator declaration introduces a user-defined conversion (User-defined conversions) which augments the pre-defined implicit and explicit conversions.

Объявление оператора преобразования, включающее ключевое слово implicit, вводит определенное пользователем неявное преобразование.A conversion operator declaration that includes the implicit keyword introduces a user-defined implicit conversion. Неявные преобразования могут происходить в различных ситуациях, включая вызовы членов функций, выражения приведения и назначения.Implicit conversions can occur in a variety of situations, including function member invocations, cast expressions, and assignments. Это описано далее в разделе неявные преобразования.This is described further in Implicit conversions.

Объявление оператора преобразования, включающее ключевое слово explicit, вводит явное преобразование, определенное пользователем.A conversion operator declaration that includes the explicit keyword introduces a user-defined explicit conversion. Явные преобразования могут происходить в выражениях приведения и описаны далее в явных преобразованиях.Explicit conversions can occur in cast expressions, and are described further in Explicit conversions.

Оператор преобразования выполняет преобразование из исходного типа, обозначенного типом параметра оператора преобразования, в целевой тип, обозначенный типом возвращаемого значения оператора преобразования.A conversion operator converts from a source type, indicated by the parameter type of the conversion operator, to a target type, indicated by the return type of the conversion operator.

Для данного типа источника S и тип целевого объекта T, если S или T являются типами, допускающими значение null, Let S0 и T0 ссылаться на их базовые типы, в противном случае S0 и T0 равны S и T соответственно.For a given source type S and target type T, if S or T are nullable types, let S0 and T0 refer to their underlying types, otherwise S0 and T0 are equal to S and T respectively. Класс или структура может объявить преобразование из исходного типа S с типом целевого объекта T только в том случае, если выполняются все следующие условия:A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true:

  • S0 и T0 относятся к разным типам.S0 and T0 are different types.
  • S0 или T0 является типом класса или структуры, в котором выполняется объявление оператора.Either S0 or T0 is the class or struct type in which the operator declaration takes place.
  • Ни S0, ни T0 не являются interface_type.Neither S0 nor T0 is an interface_type.
  • За исключением пользовательских преобразований, преобразование из S в T или из T в Sне существует.Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.

В целях этих правил все параметры типа, связанные с S или T, считаются уникальными типами, не имеющими отношений наследования с другими типами, и все ограничения на эти параметры типа игнорируются.For the purposes of these rules, any type parameters associated with S or T are considered to be unique types that have no inheritance relationship with other types, and any constraints on those type parameters are ignored.

в примереIn the example

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
}

Первые два объявления операторов разрешены, так как для индексаторов3, T и int и string соответственно считаются уникальными типами без связи.the first two operator declarations are permitted because, for the purposes of Indexers.3, T and int and string respectively are considered unique types with no relationship. Однако третий оператор является ошибкой, так как C<T> является базовым классом D<T>.However, the third operator is an error because C<T> is the base class of D<T>.

Во втором правиле следует, чтобы оператор преобразования был преобразован в класс или тип структуры, в котором объявлен оператор.From the second rule it follows that a conversion operator must convert either to or from the class or struct type in which the operator is declared. Например, тип класса или структуры можно C для определения преобразования из C в int и из int в C, но не из int в bool.For example, it is possible for a class or struct type C to define a conversion from C to int and from int to C, but not from int to bool.

Невозможно напрямую переопределить предварительно определенное преобразование.It is not possible to directly redefine a pre-defined conversion. Таким образом, операторам преобразования не разрешено преобразовывать или в object, поскольку между object и другими типами уже существуют явные и неявные преобразования.Thus, conversion operators are not allowed to convert from or to object because implicit and explicit conversions already exist between object and all other types. Аналогичным образом ни исходный, ни целевой тип преобразования не может быть базовым типом другого, так как преобразование уже будет существовать.Likewise, neither the source nor the target types of a conversion can be a base type of the other, since a conversion would then already exist.

Однако можно объявить операторы в универсальных типах, которые для конкретных аргументов типа указывают преобразования, которые уже существуют как предварительно определенные преобразования.However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions. в примереIn the example

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

Если тип object указан в качестве аргумента типа для T, второй оператор объявляет преобразование, которое уже существует (неявный, и, следовательно, также явное преобразование существует из любого типа в тип object).when type object is specified as a type argument for T, the second operator declares a conversion that already exists (an implicit, and therefore also an explicit, conversion exists from any type to type object).

В случаях, когда существует предварительно определенное преобразование между двумя типами, любые пользовательские преобразования между этими типами игнорируются.In cases where a pre-defined conversion exists between two types, any user-defined conversions between those types are ignored. В частности:Specifically:

  • Если предопределенное неявное преобразование (неявные преобразования) существует из типа S в тип T, все определяемые пользователем преобразования (неявные или явные) из S в T игнорируются.If a pre-defined implicit conversion (Implicit conversions) exists from type S to type T, all user-defined conversions (implicit or explicit) from S to T are ignored.
  • Если Tдля типа S существует предварительно определенное явное преобразование (явные преобразования), любые пользовательские явные преобразования из S в T игнорируются.If a pre-defined explicit conversion (Explicit conversions) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. Более того,Furthermore:

Если T является типом интерфейса, пользовательские неявные преобразования из S в T игнорируются.If T is an interface type, user-defined implicit conversions from S to T are ignored.

В противном случае пользовательские неявные преобразования из S в T по-прежнему считаются.Otherwise, user-defined implicit conversions from S to T are still considered.

Для всех типов, кроме object, операторы, объявленные с помощью приведенного выше типа Convertible<T>, не конфликтуют с предварительно определенными преобразованиями.For all types but object, the operators declared by the Convertible<T> type above do not conflict with pre-defined conversions. Например:For example:

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
}

Однако для типов objectпредварительно определенные преобразования скрывают пользовательские преобразования во всех случаях, кроме одного:However, for type object, pre-defined conversions hide the user-defined conversions in all cases but one:

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
}

Преобразованиям, определенным пользователем, не разрешено преобразовывать или в interface_types.User-defined conversions are not allowed to convert from or to interface_types. В частности, это ограничение гарантирует, что при преобразовании в interface_typeне происходит никаких пользовательских преобразований и что преобразование в interface_type выполняется, только если преобразуемый объект фактически реализует указанный interface_type.In particular, this restriction ensures that no user-defined transformations occur when converting to an interface_type, and that a conversion to an interface_type succeeds only if the object being converted actually implements the specified interface_type.

Сигнатура оператора преобразования состоит из исходного типа и целевого типа.The signature of a conversion operator consists of the source type and the target type. (Обратите внимание, что это единственная форма члена, для которого возвращаемый тип участвует в сигнатуре.) Классификация implicit или explicit оператора преобразования не является частью сигнатуры оператора.(Note that this is the only form of member for which the return type participates in the signature.) The implicit or explicit classification of a conversion operator is not part of the operator's signature. Таким же класс или структура не может объявить как implicit, так и explicit оператор преобразования с одинаковыми исходным и целевым типами.Thus, a class or struct cannot declare both an implicit and an explicit conversion operator with the same source and target types.

В целом, определяемые пользователем неявные преобразования должны быть спроектированы таким образом, чтобы никогда не создавать исключения и никогда не потерять информацию.In general, user-defined implicit conversions should be designed to never throw exceptions and never lose information. Если пользовательское преобразование может привести к возникновению исключений (например, из-за того, что исходный аргумент выходит за пределы допустимого диапазона) или потери информации (например, отклонения старших битов), то это преобразование должно быть определено как явное преобразование.If a user-defined conversion can give rise to exceptions (for example, because the source argument is out of range) or loss of information (such as discarding high-order bits), then that conversion should be defined as an explicit conversion.

в примереIn the example

using System;

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) {
        return d.value;
    }

    public static explicit operator Digit(byte b) {
        return new Digit(b);
    }
}

преобразование из Digit в byte является неявным, поскольку оно никогда не создает исключения или теряет информацию, но преобразование из byte в Digit является явным, так как Digit может представлять только подмножество возможных значений byte.the conversion from Digit to byte is implicit because it never throws exceptions or loses information, but the conversion from byte to Digit is explicit since Digit can only represent a subset of the possible values of a byte.

Конструкторы экземпляровInstance constructors

Конструктор экземпляра является членом, который реализует действия для инициализации нового экземпляра класса.An instance constructor is a member that implements the actions required to initialize an instance of a class. Конструкторы экземпляров объявляются с помощью constructor_declarations:Instance constructors are declared using constructor_declarations:

constructor_declaration
    : attributes? constructor_modifier* constructor_declarator constructor_body
    ;

constructor_modifier
    : 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'extern'
    | constructor_modifier_unsafe
    ;

constructor_declarator
    : identifier '(' formal_parameter_list? ')' constructor_initializer?
    ;

constructor_initializer
    : ':' 'base' '(' argument_list? ')'
    | ':' 'this' '(' argument_list? ')'
    ;

constructor_body
    : block
    | ';'
    ;

Constructor_declaration может включать набор атрибутов (атрибутов), допустимое сочетание четырех модификаторов доступа (модификаторов доступа) и модификатора extern (Внешние методы).A constructor_declaration may include a set of attributes (Attributes), a valid combination of the four access modifiers (Access modifiers), and an extern (External methods) modifier. Объявление конструктора не может включать один и тот же модификатор несколько раз.A constructor declaration is not permitted to include the same modifier multiple times.

Идентификатор constructor_declarator должен присвоить имя классу, в котором объявлен конструктор экземпляра.The identifier of a constructor_declarator must name the class in which the instance constructor is declared. Если указано любое другое имя, возникает ошибка времени компиляции.If any other name is specified, a compile-time error occurs.

Необязательный formal_parameter_list конструктора экземпляра подчиняется тем же правилам, что и Formal_parameter_list метода (методы).The optional formal_parameter_list of an instance constructor is subject to the same rules as the formal_parameter_list of a method (Methods). Список формальных параметров определяет сигнатуру (сигнатуры и перегрузку) конструктора экземпляра и управляет процессом, при котором разрешение перегрузки (вывод типа) выбирает определенный конструктор экземпляра в вызове.The formal parameter list defines the signature (Signatures and overloading) of an instance constructor and governs the process whereby overload resolution (Type inference) selects a particular instance constructor in an invocation.

Каждый из типов, на которые ссылается formal_parameter_list конструктора экземпляра, должен иметь по крайней мере такой же уровень доступности, как и сам конструктор (ограничения доступности).Each of the types referenced in the formal_parameter_list of an instance constructor must be at least as accessible as the constructor itself (Accessibility constraints).

Необязательный constructor_initializer указывает другой конструктор экземпляра для вызова перед выполнением инструкций, заданных в constructor_body данного конструктора экземпляра.The optional constructor_initializer specifies another instance constructor to invoke before executing the statements given in the constructor_body of this instance constructor. Это описано далее в разделе инициализаторы конструктора.This is described further in Constructor initializers.

Если объявление конструктора содержит модификатор extern, то этот конструктор называется внешним конструктором.When a constructor declaration includes an extern modifier, the constructor is said to be an external constructor. Поскольку объявление внешнего конструктора не предоставляет фактической реализации, его constructor_body состоит из точки с запятой.Because an external constructor declaration provides no actual implementation, its constructor_body consists of a semicolon. Для всех остальных конструкторов constructor_body состоит из блока , который задает инструкции для инициализации нового экземпляра класса.For all other constructors, the constructor_body consists of a block which specifies the statements to initialize a new instance of the class. Это точно соответствует блоку метода экземпляра с типом возвращаемого значения void (тело метода).This corresponds exactly to the block of an instance method with a void return type (Method body).

Конструкторы экземпляров не наследуются.Instance constructors are not inherited. Таким же класс не имеет конструкторов экземпляров, кроме тех, которые фактически объявлены в классе.Thus, a class has no instance constructors other than those actually declared in the class. Если класс не содержит объявлений конструкторов экземпляров, автоматически предоставляется конструктор экземпляра по умолчанию (конструкторы по умолчанию).If a class contains no instance constructor declarations, a default instance constructor is automatically provided (Default constructors).

Конструкторы экземпляров вызываются object_creation_expressions (выражениями создания объектов) и с помощью constructor_initializers.Instance constructors are invoked by object_creation_expressions (Object creation expressions) and through constructor_initializers.

Инициализаторы конструктораConstructor initializers

Все конструкторы экземпляров (за исключением классов для objectкласса) неявно включают вызов другого конструктора экземпляра непосредственно перед constructor_body.All instance constructors (except those for class object) implicitly include an invocation of another instance constructor immediately before the constructor_body. Конструктор для неявного вызова определяется constructor_initializer:The constructor to implicitly invoke is determined by the constructor_initializer:

  • Инициализатор конструктора экземпляров формы base(argument_list) или base() вызывает вызов конструктора экземпляра из прямого базового класса.An instance constructor initializer of the form base(argument_list) or base() causes an instance constructor from the direct base class to be invoked. Этот конструктор выбирается с помощью argument_list при наличии и правил разрешения перегрузки для разрешения перегрузки.That constructor is selected using argument_list if present and the overload resolution rules of Overload resolution. Набор конструкторов экземпляров-кандидатов состоит из всех доступных конструкторов экземпляров, содержащихся в прямом базовом классе, или конструктора по умолчанию (конструкторов по умолчанию), если конструкторы экземпляров не объявлены в прямом базовом классе.The set of candidate instance constructors consists of all accessible instance constructors contained in the direct base class, or the default constructor (Default constructors), if no instance constructors are declared in the direct base class. Если этот набор пуст или не удается определить один лучший конструктор экземпляров, возникает ошибка времени компиляции.If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs.
  • Инициализатор конструктора экземпляров формы this(argument-list) или this() вызывает вызов конструктора экземпляра из самого класса.An instance constructor initializer of the form this(argument-list) or this() causes an instance constructor from the class itself to be invoked. Конструктор выбирается с помощью argument_list при наличии и правил разрешения перегрузки для разрешения перегрузки.The constructor is selected using argument_list if present and the overload resolution rules of Overload resolution. Набор конструкторов экземпляров кандидатов состоит из всех доступных конструкторов экземпляров, объявленных в самом классе.The set of candidate instance constructors consists of all accessible instance constructors declared in the class itself. Если этот набор пуст или не удается определить один лучший конструктор экземпляров, возникает ошибка времени компиляции.If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs. Если объявление конструктора экземпляра содержит инициализатор конструктора, который вызывает сам конструктор, возникает ошибка времени компиляции.If an instance constructor declaration includes a constructor initializer that invokes the constructor itself, a compile-time error occurs.

Если конструктор экземпляра не имеет инициализатора конструктора, неявно предоставляется инициализатор конструктора формы base().If an instance constructor has no constructor initializer, a constructor initializer of the form base() is implicitly provided. Таким словами, объявление конструктора экземпляра формыThus, an instance constructor declaration of the form

C(...) {...}

в точности эквивалентноis exactly equivalent to

C(...): base() {...}

Область параметров, заданная formal_parameter_list объявления конструктора экземпляра, включает инициализатор конструктора этого объявления.The scope of the parameters given by the formal_parameter_list of an instance constructor declaration includes the constructor initializer of that declaration. Таким словами, инициализатору конструктора разрешен доступ к параметрам конструктора.Thus, a constructor initializer is permitted to access the parameters of the constructor. Например:For example:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y): base(x + y, x - y) {}
}

Инициализатор конструктора экземпляров не может получить доступ к создаваемому экземпляру.An instance constructor initializer cannot access the instance being created. Поэтому при ссылке на this в выражении аргумента инициализатора конструктора возникает ошибка времени компиляции, так как это ошибка времени компиляции для выражения аргумента, ссылающегося на любой член экземпляра через simple_name.Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple_name.

Инициализаторы переменных экземпляровInstance variable initializers

Если конструктор экземпляра не имеет инициализатора конструктора или имеет инициализатор конструктора формы base(...), этот конструктор неявно выполняет инициализацию, заданную variable_initializers из полей экземпляра, объявленных в его классе.When an instance constructor has no constructor initializer, or it has a constructor initializer of the form base(...), that constructor implicitly performs the initializations specified by the variable_initializers of the instance fields declared in its class. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор и перед неявным вызовом конструктора прямого базового класса.This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. Инициализаторы переменных выполняются в текстовом порядке, в котором они отображаются в объявлении класса.The variable initializers are executed in the textual order in which they appear in the class declaration.

Выполнение конструктораConstructor execution

Инициализаторы переменных преобразуются в операторы присваивания, и эти операторы присваивания выполняются до вызова конструктора экземпляра базового класса.Variable initializers are transformed into assignment statements, and these assignment statements are executed before the invocation of the base class instance constructor. Этот порядок гарантирует, что все поля экземпляров инициализируются их инициализаторами переменных перед выполнением любых инструкций, имеющих доступ к этому экземпляру.This ordering ensures that all instance fields are initialized by their variable initializers before any statements that have access to that instance are executed.

Учитывая примерGiven the example

using System;

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 = {0}, y = {1}", x, y);
    }
}

Если для создания экземпляра Bиспользуется new B(), создаются следующие выходные данные:when new B() is used to create an instance of B, the following output is produced:

x = 1, y = 0

Значение x равно 1, так как инициализатор переменной выполняется перед вызовом конструктора экземпляра базового класса.The value of x is 1 because the variable initializer is executed before the base class instance constructor is invoked. Однако значение y равно 0 (значение по умолчанию для int), так как присваивание y не выполняется до тех пор, пока не будет возвращен конструктор базового класса.However, the value of y is 0 (the default value of an int) because the assignment to y is not executed until after the base class constructor returns.

Инициализаторы переменных экземпляров и инициализаторы конструкторов удобно использовать в качестве инструкций, которые автоматически вставляются перед constructor_body.It is useful to think of instance variable initializers and constructor initializers as statements that are automatically inserted before the constructor_body. ПримерThe example

using System;
using System.Collections;

class A
{
    int x = 1, y = -1, count;

    public A() {
        count = 0;
    }

    public A(int n) {
        count = n;
    }
}

class B: A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100) {
        items.Add("default");
    }

    public B(int n): base(n - 1) {
        max = n;
    }
}

содержит несколько инициализаторов переменных; Он также содержит инициализаторы конструкторов обеих форм (base и this).contains several variable initializers; it also contains constructor initializers of both forms (base and this). Пример соответствует коду, показанному ниже, где каждый комментарий указывает на автоматически вставленную инструкцию (синтаксис, используемый для автоматически вставляемых вызовов конструктора, является недопустимым, но просто служит иллюстрацией к механизму).The example corresponds to the code shown below, where each comment indicates an automatically inserted statement (the syntax used for the automatically inserted constructor invocations isn't valid, but merely serves to illustrate the mechanism).

using System.Collections;

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;
    }
}

Конструкторы по умолчаниюDefault constructors

Если класс не содержит объявлений конструктора экземпляров, автоматически предоставляется конструктор экземпляра по умолчанию.If a class contains no instance constructor declarations, a default instance constructor is automatically provided. Этот конструктор по умолчанию просто вызывает конструктор без параметров прямого базового класса.That default constructor simply invokes the parameterless constructor of the direct base class. Если класс является абстрактным, то для конструктора по умолчанию будет защищен объявленный уровень доступа.If the class is abstract then the declared accessibility for the default constructor is protected. В противном случае объявленный уровень доступности для конструктора по умолчанию — Public.Otherwise, the declared accessibility for the default constructor is public. Таким же конструктором по умолчанию всегда является формаThus, the default constructor is always of the form

protected C(): base() {}

илиor

public C(): base() {}

где C — имя класса.where C is the name of the class. Если разрешение перегрузки не может определить уникально лучшее решение для инициализатора конструктора базового класса, возникает ошибка времени компиляции.If overload resolution is unable to determine a unique best candidate for the base class constructor initializer then a compile-time error occurs.

в примереIn the example

class Message
{
    object sender;
    string text;
}

предоставляется конструктор по умолчанию, поскольку класс не содержит объявлений конструктора экземпляров.a default constructor is provided because the class contains no instance constructor declarations. Таким образом, пример в точности эквивалентенThus, the example is precisely equivalent to

class Message
{
    object sender;
    string text;

    public Message(): base() {}
}

Закрытые конструкторыPrivate constructors

Если класс T объявляет только конструкторы закрытых экземпляров, классы, находящиеся за пределами текста программы T, не могут быть производными от T или напрямую создавать экземпляры T.When a class T declares only private instance constructors, it is not possible for classes outside the program text of T to derive from T or to directly create instances of T. Таким образом, если класс содержит только статические члены и не предназначен для создания экземпляра, то добавление пустого закрытого конструктора экземпляра предотвратит создание экземпляра.Thus, if a class contains only static members and isn't intended to be instantiated, adding an empty private instance constructor will prevent instantiation. Например:For example:

public class Trig
{
    private Trig() {}        // Prevent instantiation

    public const double PI = 3.14159265358979323846;

    public static double Sin(double x) {...}
    public static double Cos(double x) {...}
    public static double Tan(double x) {...}
}

Класс Trig группирует связанные методы и константы, но не предназначен для создания экземпляра.The Trig class groups related methods and constants, but is not intended to be instantiated. Поэтому он объявляет один пустой конструктор экземпляра Private.Therefore it declares a single empty private instance constructor. Для подавления автоматического создания конструктора по умолчанию необходимо объявить хотя бы один конструктор экземпляров.At least one instance constructor must be declared to suppress the automatic generation of a default constructor.

Необязательные параметры конструктора экземпляраOptional instance constructor parameters

this(...) форма инициализатора конструктора обычно используется в сочетании с перегрузкой для реализации параметров конструктора необязательных экземпляров.The this(...) form of constructor initializer is commonly used in conjunction with overloading to implement optional instance constructor parameters. в примереIn the example

class Text
{
    public Text(): this(0, 0, null) {}

    public Text(int x, int y): this(x, y, null) {}

    public Text(int x, int y, string s) {
        // Actual constructor implementation
    }
}

Первые два конструктора экземпляров просто предоставляют значения по умолчанию для отсутствующих аргументов.the first two instance constructors merely provide the default values for the missing arguments. В обоих случаях используется инициализатор конструктора this(...) для вызова третьего конструктора экземпляра, который фактически выполняет инициализацию нового экземпляра.Both use a this(...) constructor initializer to invoke the third instance constructor, which actually does the work of initializing the new instance. Результатом является необязательные параметры конструктора:The effect is that of optional constructor parameters:

Text t1 = new Text();                    // Same as Text(0, 0, null)
Text t2 = new Text(5, 10);               // Same as Text(5, 10, null)
Text t3 = new Text(5, 20, "Hello");

Статические конструкторыStatic constructors

Статический конструктор — это член, реализующий действия, необходимые для инициализации закрытого типа класса.A static constructor is a member that implements the actions required to initialize a closed class type. Статические конструкторы объявляются с помощью static_constructor_declarations:Static constructors are declared using static_constructor_declarations:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')' static_constructor_body
    ;

static_constructor_modifiers
    : 'extern'? 'static'
    | 'static' 'extern'?
    | static_constructor_modifiers_unsafe
    ;

static_constructor_body
    : block
    | ';'
    ;

Static_constructor_declaration может включать набор атрибутов (атрибутов) и модификатор extern (Внешние методы).A static_constructor_declaration may include a set of attributes (Attributes) and an extern modifier (External methods).

Идентификатор static_constructor_declaration должен присвоить имя классу, в котором объявлен статический конструктор.The identifier of a static_constructor_declaration must name the class in which the static constructor is declared. Если указано любое другое имя, возникает ошибка времени компиляции.If any other name is specified, a compile-time error occurs.

Если объявление статического конструктора содержит модификатор extern, то статический конструктор называется внешним статическим конструктором.When a static constructor declaration includes an extern modifier, the static constructor is said to be an external static constructor. Поскольку объявление внешнего статического конструктора не предоставляет фактической реализации, его static_constructor_body состоит из точки с запятой.Because an external static constructor declaration provides no actual implementation, its static_constructor_body consists of a semicolon. Для всех других объявлений статических конструкторов static_constructor_body состоит из блока , который задает операторы для выполнения, чтобы инициализировать класс.For all other static constructor declarations, the static_constructor_body consists of a block which specifies the statements to execute in order to initialize the class. Это точно соответствует method_body статического метода с типом возвращаемого значения void (тело метода).This corresponds exactly to the method_body of a static method with a void return type (Method body).

Статические конструкторы не наследуются и не могут вызываться напрямую.Static constructors are not inherited, and cannot be called directly.

Статический конструктор для закрытого типа класса выполняется не более одного раза в заданном домене приложения.The static constructor for a closed class type executes at most once in a given application domain. Выполнение статического конструктора активируется первым из следующих событий, происходящих в домене приложения:The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

  • Создается экземпляр типа класса.An instance of the class type is created.
  • Имеются ссылки на любые статические члены типа класса.Any of the static members of the class type are referenced.

Если класс содержит метод Main (Запуск приложения), в котором начинается выполнение, статический конструктор для этого класса выполняется до вызова метода Main.If a class contains the Main method (Application Startup) in which execution begins, the static constructor for that class executes before the Main method is called.

Чтобы инициализировать новый тип закрытого класса, сначала создается новый набор статических полей (статические и поля экземпляров) для этого конкретного закрытого типа.To initialize a new closed class type, first a new set of static fields (Static and instance fields) for that particular closed type is created. Каждое из статических полей инициализируется значением по умолчанию (значения по умолчанию).Each of the static fields is initialized to its default value (Default values). Затем для этих статических полей выполняются инициализаторы статических полей (Инициализация статических полей).Next, the static field initializers (Static field initialization) are executed for those static fields. Наконец, выполняется статический конструктор.Finally, the static constructor is executed.

ПримерThe example

using System;

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");
    }
}

необходимо создать выходные данные:must produce the output:

Init A
A.F
Init B
B.F

Поскольку выполнение статического конструктора Aактивируется вызовом A.F, а выполнение статического конструктора Bинициируется вызовом B.F.because the execution of A's static constructor is triggered by the call to A.F, and the execution of B's static constructor is triggered by the call to B.F.

Можно создать циклические зависимости, позволяющие использовать статические поля с инициализаторами переменных в состоянии значения по умолчанию.It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state.

ПримерThe example

using System;

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 = {0}, Y = {1}", A.X, B.Y);
    }
}

выводятся следующие выходные данныеproduces the output

X = 1, Y = 2

Чтобы выполнить метод Main, система сначала запускает инициализатор для B.Y, перед статическим конструктором класса B.To execute the Main method, the system first runs the initializer for B.Y, prior to class B's static constructor. инициализатор Yвызывает выполнение статического конструктора A, так как на значение A.X указывает ссылка.Y's initializer causes A's static constructor to be run because the value of A.X is referenced. Статический конструктор A, в свою очередь, продолжает вычислять значение X, и при этом получает значение по умолчанию Y, равное нулю.The static constructor of A in turn proceeds to compute the value of X, and in doing so fetches the default value of Y, which is zero. Таким же A.X инициализируется значением 1.A.X is thus initialized to 1. Процесс выполнения инициализаторов статических полей Aи статического конструктора завершается, возвращая результат вычисления начального значения Y, результатом которого станет 2.The process of running A's static field initializers and static constructor then completes, returning to the calculation of the initial value of Y, the result of which becomes 2.

Поскольку статический конструктор выполняется только один раз для каждого закрытого сконструированного типа класса, это удобное место для принудительной проверки параметра типа, который не может быть проверен во время компиляции с помощью ограничений (ограничений на параметры типа).Because the static constructor is executed exactly once for each closed constructed class type, it is a convenient place to enforce run-time checks on the type parameter that cannot be checked at compile-time via constraints (Type parameter constraints). Например, следующий тип использует статический конструктор для обеспечения того, что аргумент типа является перечислением:For example, the following type uses a static constructor to enforce that the type argument is an enum:

class Gen<T> where T: struct
{
    static Gen() {
        if (!typeof(T).IsEnum) {
            throw new ArgumentException("T must be an enum");
        }
    }
}

Деструкторы.Destructors

Деструктор — это член, реализующий действия, необходимые для уничтожения экземпляра класса.A destructor is a member that implements the actions required to destruct an instance of a class. Деструктор объявляется с помощью destructor_declaration:A destructor is declared using a destructor_declaration:

destructor_declaration
    : attributes? 'extern'? '~' identifier '(' ')' destructor_body
    | destructor_declaration_unsafe
    ;

destructor_body
    : block
    | ';'
    ;

Destructor_declaration может включать набор атрибутов (атрибутов).A destructor_declaration may include a set of attributes (Attributes).

Идентификатор destructor_declaration должен присвоить имя классу, в котором объявлен деструктор.The identifier of a destructor_declaration must name the class in which the destructor is declared. Если указано любое другое имя, возникает ошибка времени компиляции.If any other name is specified, a compile-time error occurs.

Если объявление деструктора содержит модификатор extern, то деструктор называется внешним деструктором.When a destructor declaration includes an extern modifier, the destructor is said to be an external destructor. Поскольку объявление внешнего деструктора не предоставляет фактической реализации, его destructor_body состоит из точки с запятой.Because an external destructor declaration provides no actual implementation, its destructor_body consists of a semicolon. Для всех других деструкторов destructor_body состоит из блока , который задает инструкции для выполнения, чтобы уничтожения экземпляр класса.For all other destructors, the destructor_body consists of a block which specifies the statements to execute in order to destruct an instance of the class. Destructor_body точно соответствует method_body метода экземпляра с типом возвращаемого значения void (тело метода).A destructor_body corresponds exactly to the method_body of an instance method with a void return type (Method body).

Деструкторы не наследуются.Destructors are not inherited. Таким же класс не имеет деструкторов, отличных от тех, которые могут быть объявлены в этом классе.Thus, a class has no destructors other than the one which may be declared in that class.

Поскольку деструктор должен не иметь параметров, он не может быть перегружен, поэтому класс может иметь не более одного деструктора.Since a destructor is required to have no parameters, it cannot be overloaded, so a class can have, at most, one destructor.

Деструкторы вызываются автоматически, и их нельзя вызывать явным образом.Destructors are invoked automatically, and cannot be invoked explicitly. Экземпляр становится пригодным для уничтожения, если он больше не может использовать какой-либо код для использования этого экземпляра.An instance becomes eligible for destruction when it is no longer possible for any code to use that instance. Выполнение деструктора для экземпляра может произойти в любое время после того, как экземпляр станет пригодным для уничтожения.Execution of the destructor for the instance may occur at any time after the instance becomes eligible for destruction. При девязке экземпляра деструкторы в цепочке наследования этого экземпляра вызываются по порядку от самого дальнего до наименее производного.When an instance is destructed, the destructors in that instance's inheritance chain are called, in order, from most derived to least derived. Деструктор может выполняться в любом потоке.A destructor may be executed on any thread. Дополнительные сведения о правилах, определяющих время и принцип выполнения деструктора, см. в статье Автоматическое управление памятью.For further discussion of the rules that govern when and how a destructor is executed, see Automatic memory management.

Выходные данные примераThe output of the example

using System;

class A
{
    ~A() {
        Console.WriteLine("A's destructor");
    }
}

class B: A
{
    ~B() {
        Console.WriteLine("B's destructor");
    }
}

class Test
{
   static void Main() {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
   }
}

isis

B's destructor
A's destructor

так как деструкторы в цепочке наследования вызываются по порядку, от наиболее производного до наименее производного.since destructors in an inheritance chain are called in order, from most derived to least derived.

Деструкторы реализуются путем переопределения виртуального метода Finalize в System.Object.Destructors are implemented by overriding the virtual method Finalize on System.Object. C#программам не разрешается переопределять этот метод или вызывать его (или переопределения) напрямую.C# programs are not permitted to override this method or call it (or overrides of it) directly. Например, программаFor instance, the program

class A 
{
    override protected void Finalize() {}    // error

    public void F() {
        this.Finalize();                     // error
    }
}

содержит две ошибки.contains two errors.

Компилятор ведет себя так, как если бы этот метод и его переопределения не существовали.The compiler behaves as if this method, and overrides of it, do not exist at all. Таким образом, эта программа:Thus, this program:

class A 
{
    void Finalize() {}                            // permitted
}

является допустимым, и показанный метод скрывает метод Finalize System.Object.is valid, and the method shown hides System.Object's Finalize method.

Описание поведения, возникающих при возникновении исключения из деструктора, см. в разделе обработка исключений.For a discussion of the behavior when an exception is thrown from a destructor, see How exceptions are handled.

ИтераторыIterators

Функция-член (функции-члены), реализованная с помощью блока итератора (Blocks), называется итератором.A function member (Function members) implemented using an iterator block (Blocks) is called an iterator.

Блок итератора может использоваться в качестве тела члена-функции, если тип возвращаемого значения соответствующего члена функции является одним из интерфейсов перечислителя (интерфейсов перечислителя) или одним из перечисляемых интерфейсов (перечислимые интерфейсы).An iterator block may be used as the body of a function member as long as the return type of the corresponding function member is one of the enumerator interfaces (Enumerator interfaces) or one of the enumerable interfaces (Enumerable interfaces). Это может быть method_body, operator_body или accessor_body, тогда как события, конструкторы экземпляров, статические конструкторы и деструкторы не могут быть реализованы как итераторы.It can occur as a method_body, operator_body or accessor_body, whereas events, instance constructors, static constructors and destructors cannot be implemented as iterators.

Если член функции реализуется с помощью блока итератора, то это ошибка времени компиляции для списка формальных параметров члена функции с указанием любых ref или out параметров.When a function member is implemented using an iterator block, it is a compile-time error for the formal parameter list of the function member to specify any ref or out parameters.

Интерфейсы перечислителяEnumerator interfaces

Интерфейсы перечислителя являются неуниверсальным интерфейсом System.Collections.IEnumerator и всеми экземплярами универсального интерфейса System.Collections.Generic.IEnumerator<T>.The enumerator interfaces are the non-generic interface System.Collections.IEnumerator and all instantiations of the generic interface System.Collections.Generic.IEnumerator<T>. Для краткости в этой главе эти интерфейсы указываются как IEnumerator и IEnumerator<T>соответственно.For the sake of brevity, in this chapter these interfaces are referenced as IEnumerator and IEnumerator<T>, respectively.

Перечислимые интерфейсыEnumerable interfaces

Перечислимые интерфейсы являются неуниверсальным интерфейсом System.Collections.IEnumerable и всеми экземплярами универсального интерфейса System.Collections.Generic.IEnumerable<T>.The enumerable interfaces are the non-generic interface System.Collections.IEnumerable and all instantiations of the generic interface System.Collections.Generic.IEnumerable<T>. Для краткости в этой главе эти интерфейсы указываются как IEnumerable и IEnumerable<T>соответственно.For the sake of brevity, in this chapter these interfaces are referenced as IEnumerable and IEnumerable<T>, respectively.

Тип yieldYield type

Итератор создает последовательность значений одного и того же типа.An iterator produces a sequence of values, all of the same type. Этот тип называется типом yield итератора.This type is called the yield type of the iterator.

  • Тип получения итератора, который возвращает IEnumerator или IEnumerable, является object.The yield type of an iterator that returns IEnumerator or IEnumerable is object.
  • Тип получения итератора, который возвращает IEnumerator<T> или IEnumerable<T>, является T.The yield type of an iterator that returns IEnumerator<T> or IEnumerable<T> is T.

Объекты перечислителяEnumerator objects

Когда член функции, возвращающий тип интерфейса перечислителя, реализуется с помощью блока итератора, вызов функции-члена не приводит к немедленному выполнению кода в блоке итератора.When a function member returning an enumerator interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Вместо этого создается и возвращается объект перечислителя .Instead, an enumerator object is created and returned. Этот объект инкапсулирует код, указанный в блоке итератора, и выполнение кода в блоке итератора происходит при вызове метода MoveNext объекта перечислителя.This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object's MoveNext method is invoked. Объект перечислителя имеет следующие характеристики.An enumerator object has the following characteristics:

  • Он реализует IEnumerator и IEnumerator<T>, где T является типом yield итератора.It implements IEnumerator and IEnumerator<T>, where T is the yield type of the iterator.
  • Он реализует System.IDisposable.It implements System.IDisposable.
  • Он инициализируется копией значений аргументов (при их наличии) и значением экземпляра, переданным члену функции.It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
  • Он имеет четыре возможных состояния: до, Запуск, приостановлени после, и изначально находится в состоянии Before .It has four potential states, before, running, suspended, and after, and is initially in the before state.

Объект перечислителя обычно является экземпляром класса перечислителя, созданного компилятором, который инкапсулирует код в блоке итератора и реализует интерфейсы перечислителя, но можно реализовать и другие методы реализации.An enumerator object is typically an instance of a compiler-generated enumerator class that encapsulates the code in the iterator block and implements the enumerator interfaces, but other methods of implementation are possible. Если компилятор создает класс перечислителя, то этот класс будет вложенным, прямым или косвенным, в классе, содержащем член функции, он будет иметь закрытый уровень доступности и будет иметь имя, зарезервированное для использования компилятором (идентификаторы).If an enumerator class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use (Identifiers).

Объект перечислителя может реализовывать больше интерфейсов, чем указано выше.An enumerator object may implement more interfaces than those specified above.

В следующих разделах описывается точное поведение MoveNext, Currentи Dispose членов реализаций интерфейса IEnumerable и IEnumerable<T>, предоставляемых объектом перечислителя.The following sections describe the exact behavior of the MoveNext, Current, and Dispose members of the IEnumerable and IEnumerable<T> interface implementations provided by an enumerator object.

Обратите внимание, что объекты перечислителя не поддерживают метод IEnumerator.Reset.Note that enumerator objects do not support the IEnumerator.Reset method. Вызов этого метода приводит к возникновению System.NotSupportedException.Invoking this method causes a System.NotSupportedException to be thrown.

Метод MoveNextThe MoveNext method

Метод MoveNext объекта перечислителя инкапсулирует код блока итератора.The MoveNext method of an enumerator object encapsulates the code of an iterator block. При вызове метода MoveNext выполняется код в блоке итератора и устанавливается соответствующее свойство Current объекта перечислителя.Invoking the MoveNext method executes code in the iterator block and sets the Current property of the enumerator object as appropriate. Точное действие, выполняемое MoveNext, зависит от состояния объекта перечислителя при вызове MoveNext.The precise action performed by MoveNext depends on the state of the enumerator object when MoveNext is invoked:

  • Если состояние объекта перечислителя предшествует, вызывается MoveNext:If the state of the enumerator object is before, invoking MoveNext:
    • Изменяет состояние на работает.Changes the state to running.
    • Инициализирует параметры (включая this) блока итератора на значения аргументов и значение экземпляра, сохраненное при инициализации объекта перечислителя.Initializes the parameters (including this) of the iterator block to the argument values and instance value saved when the enumerator object was initialized.
    • Выполняет блок итератора из начала до тех пор, пока выполнение не будет прервано (как описано ниже).Executes the iterator block from the beginning until execution is interrupted (as described below).
  • Если состояние объекта перечислителя работает, результат вызова MoveNext не определен.If the state of the enumerator object is running, the result of invoking MoveNext is unspecified.
  • Если состояние объекта перечислителя приостановлено, вызывается MoveNext:If the state of the enumerator object is suspended, invoking MoveNext:
    • Изменяет состояние на работает.Changes the state to running.
    • Восстанавливает значения всех локальных переменных и параметров (включая это) в значения, сохраненные при последней приостановке выполнения блока итератора.Restores the values of all local variables and parameters (including this) to the values saved when execution of the iterator block was last suspended. Обратите внимание, что содержимое всех объектов, на которые ссылаются эти переменные, могло измениться с момента предыдущего вызова MoveNext.Note that the contents of any objects referenced by these variables may have changed since the previous call to MoveNext.
    • Возобновляет выполнение блока итератора сразу после оператора yield return, вызвавшего приостановку выполнения, и продолжается до тех пор, пока выполнение не будет прервано (как описано ниже).Resumes execution of the iterator block immediately following the yield return statement that caused the suspension of execution and continues until execution is interrupted (as described below).
  • Если состояние объекта перечислителя находится после, вызов MoveNext возвращает false.If the state of the enumerator object is after, invoking MoveNext returns false.

Когда MoveNext выполняет блок итератора, выполнение может быть прервано четырьмя способами: инструкцией yield return, инструкцией yield break, путем обнаружения конца блока итератора и вызываемым исключением и распространением из блока итератора.When MoveNext executes the iterator block, execution can be interrupted in four ways: By a yield return statement, by a yield break statement, by encountering the end of the iterator block, and by an exception being thrown and propagated out of the iterator block.

  • При обнаружении оператора yield return (оператор yield):When a yield return statement is encountered (The yield statement):
    • Выражение, заданное в операторе, вычисляется, неявно преобразуется в тип yield и назначается свойству Current объекта перечислителя.The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the Current property of the enumerator object.
    • Выполнение тела итератора приостановлено.Execution of the iterator body is suspended. Значения всех локальных переменных и параметров (включая this) сохраняются, как и расположение этой инструкции yield return.The values of all local variables and parameters (including this) are saved, as is the location of this yield return statement. Если инструкция yield return находится в одном или нескольких блоках try, связанные блоки finally не выполняются в данный момент.If the yield return statement is within one or more try blocks, the associated finally blocks are not executed at this time.
    • Состояние объекта перечислителя изменяется на suspended.The state of the enumerator object is changed to suspended.
    • Метод MoveNext возвращает true вызывающему объекту, что означает, что итерация успешно была расширена до следующего значения.The MoveNext method returns true to its caller, indicating that the iteration successfully advanced to the next value.
  • При обнаружении оператора yield break (оператор yield):When a yield break statement is encountered (The yield statement):
    • Если инструкция yield break находится в одном или нескольких блоках try, выполняются связанные блоки finally.If the yield break statement is within one or more try blocks, the associated finally blocks are executed.
    • Состояние объекта перечислителя изменяется на After.The state of the enumerator object is changed to after.
    • Метод MoveNext возвращает false вызывающему объекту, указывая, что итерация завершена.The MoveNext method returns false to its caller, indicating that the iteration is complete.
  • При обнаружении конца тела итератора:When the end of the iterator body is encountered:
    • Состояние объекта перечислителя изменяется на After.The state of the enumerator object is changed to after.
    • Метод MoveNext возвращает false вызывающему объекту, указывая, что итерация завершена.The MoveNext method returns false to its caller, indicating that the iteration is complete.
  • При возникновении исключения и распространении из блока итератора:When an exception is thrown and propagated out of the iterator block:
    • Соответствующие блоки finally в тексте итератора будут выполнены при распространении исключения.Appropriate finally blocks in the iterator body will have been executed by the exception propagation.
    • Состояние объекта перечислителя изменяется на After.The state of the enumerator object is changed to after.
    • Распространение исключения переходит к вызывающему объекту метода MoveNext.The exception propagation continues to the caller of the MoveNext method.

Текущее свойствоThe Current property

На свойство Current объекта перечислителя влияет yield return инструкции в блоке итератора.An enumerator object's Current property is affected by yield return statements in the iterator block.

Если объект перечислителя находится в состоянии suspended , значением Current является значение, заданное предыдущим вызовом функции MoveNext.When an enumerator object is in the suspended state, the value of Current is the value set by the previous call to MoveNext. Если объект перечислителя находится в состояниях Before, выполняетсяили After , то результат доступа к Current не указан.When an enumerator object is in the before, running, or after states, the result of accessing Current is unspecified.

Для итератора с типом yield, отличным от object, результат доступа Current через реализацию IEnumerable объекта перечислителя соответствует доступу Current через реализацию IEnumerator<T> объекта перечислителя и приведение результата к object.For an iterator with a yield type other than object, the result of accessing Current through the enumerator object's IEnumerable implementation corresponds to accessing Current through the enumerator object's IEnumerator<T> implementation and casting the result to object.

Метод DisposeThe Dispose method

Метод Dispose используется для очистки итерации путем перевода объекта перечислителя в состояние After .The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.

  • Если состояние объекта перечислителя предшествует, то вызов Dispose изменяет состояние на After.If the state of the enumerator object is before, invoking Dispose changes the state to after.
  • Если состояние объекта перечислителя работает, результат вызова Dispose не определен.If the state of the enumerator object is running, the result of invoking Dispose is unspecified.
  • Если состояние объекта перечислителя приостановлено, вызывается Dispose:If the state of the enumerator object is suspended, invoking Dispose:
    • Изменяет состояние на работает.Changes the state to running.
    • Выполняет все блоки finally, как если бы последняя выполненная инструкция yield return была инструкцией yield break.Executes any finally blocks as if the last executed yield return statement were a yield break statement. Если это приводит к возникновению исключения и распространению из тела итератора, состояние объекта перечислителя устанавливается в значение After , а исключение распространяется на вызывающий метод Dispose.If this causes an exception to be thrown and propagated out of the iterator body, the state of the enumerator object is set to after and the exception is propagated to the caller of the Dispose method.
    • Изменяет состояние на After.Changes the state to after.
  • Если состояние объекта перечислителя находится в состоянии After, вызов Dispose не влияет на.If the state of the enumerator object is after, invoking Dispose has no affect.

Перечислимые объектыEnumerable objects

Когда член функции, возвращающий перечисляемый тип интерфейса, реализуется с помощью блока итератора, вызов функции-члена не приводит к немедленному выполнению кода в блоке итератора.When a function member returning an enumerable interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Вместо этого создается и возвращается перечислимый объект .Instead, an enumerable object is created and returned. Метод GetEnumerator объекта Enumerable возвращает объект перечислителя, который инкапсулирует код, указанный в блоке итератора, и выполнение кода в блоке итератора происходит при вызове метода MoveNext объекта перечислителя.The enumerable object's GetEnumerator method returns an enumerator object that encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object's MoveNext method is invoked. Перечислимый объект имеет следующие характеристики.An enumerable object has the following characteristics:

  • Он реализует IEnumerable и IEnumerable<T>, где T является типом yield итератора.It implements IEnumerable and IEnumerable<T>, where T is the yield type of the iterator.
  • Он инициализируется копией значений аргументов (при их наличии) и значением экземпляра, переданным члену функции.It is initialized with a copy of the argument values (if any) and instance value passed to the function member.

Перечислимый объект обычно является экземпляром создаваемого компилятором перечислимого класса, который инкапсулирует код в блоке итератора и реализует перечислимые интерфейсы, но возможны и другие методы реализации.An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. Если компилятор создает перечислимый класс, этот класс будет вложенным, прямым или косвенным, в классе, содержащем член функции, он будет иметь закрытый уровень доступности и будет иметь имя, зарезервированное для использования компилятором (идентификаторы).If an enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use (Identifiers).

Перечислимый объект может реализовать больше интерфейсов, чем указано выше.An enumerable object may implement more interfaces than those specified above. В частности, перечисляемый объект также может реализовывать IEnumerator и IEnumerator<T>, что позволяет ему выступать как из перечислимого, так и от перечислителя.In particular, an enumerable object may also implement IEnumerator and IEnumerator<T>, enabling it to serve as both an enumerable and an enumerator. В реализации этого типа при первом вызове метода GetEnumerator перечислимого объекта возвращается сам перечислимый объект.In that type of implementation, the first time an enumerable object's GetEnumerator method is invoked, the enumerable object itself is returned. Последующие вызовы GetEnumeratorперечислимого объекта, если таковые имеются, возвращают копию перечислимого объекта.Subsequent invocations of the enumerable object's GetEnumerator, if any, return a copy of the enumerable object. Таким же, каждый возвращаемый перечислитель имеет собственное состояние, и изменения в одном перечислителе не повлияют на другой.Thus, each returned enumerator has its own state and changes in one enumerator will not affect another.

Метод GetEnumeratorThe GetEnumerator method

Перечислимый объект предоставляет реализацию методов GetEnumerator интерфейсов IEnumerable и IEnumerable<T>.An enumerable object provides an implementation of the GetEnumerator methods of the IEnumerable and IEnumerable<T> interfaces. Два метода GetEnumerator используют общую реализацию, которая получает и возвращает доступный объект перечислителя.The two GetEnumerator methods share a common implementation that acquires and returns an available enumerator object. Объект перечислителя инициализируется значениями аргументов и значения экземпляра, сохраненными при инициализации перечислимого объекта, но в противном случае объект перечислителя вызывает функции, как описано в разделе объекты перечислителя.The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in Enumerator objects.

Пример реализацииImplementation example

В этом разделе описывается возможная реализация итераторов с точки зрения стандартных C# конструкций.This section describes a possible implementation of iterators in terms of standard C# constructs. Описанная здесь реализация основана на тех же принципах, что и компилятор C# Майкрософт, но не является обязательной реализацией или единственной возможной.The implementation described here is based on the same principles used by the Microsoft C# compiler, but it is by no means a mandated implementation or the only one possible.

Следующий класс Stack<T> реализует свой метод GetEnumerator с помощью итератора.The following Stack<T> class implements its GetEnumerator method using an iterator. Итератор перечисляет элементы стека в порядке сверху вниз.The iterator enumerates the elements of the stack in top to bottom order.

using System;
using System.Collections;
using System.Collections.Generic;

class Stack<T>: IEnumerable<T>
{
    T[] items;
    int count;

    public void Push(T item) {
        if (items == null) {
            items = new T[4];
        }
        else if (items.Length == count) {
            T[] newItems = new T[count * 2];
            Array.Copy(items, 0, newItems, 0, count);
            items = newItems;
        }
        items[count++] = item;
    }

    public T Pop() {
        T result = items[--count];
        items[count] = default(T);
        return result;
    }

    public IEnumerator<T> GetEnumerator() {
        for (int i = count - 1; i >= 0; --i) yield return items[i];
    }
}

Метод GetEnumerator можно преобразовать в экземпляр класса перечислителя, созданного компилятором, который инкапсулирует код в блоке итератора, как показано ниже.The GetEnumerator method can be translated into an instantiation of a compiler-generated enumerator class that encapsulates the code in the iterator block, as shown in the following.

class Stack<T>: IEnumerable<T>
{
    ...

    public IEnumerator<T> GetEnumerator() {
        return new __Enumerator1(this);
    }

    class __Enumerator1: IEnumerator<T>, IEnumerator
    {
        int __state;
        T __current;
        Stack<T> __this;
        int i;

        public __Enumerator1(Stack<T> __this) {
            this.__this = __this;
        }

        public T Current {
            get { return __current; }
        }

        object IEnumerator.Current {
            get { return __current; }
        }

        public bool MoveNext() {
            switch (__state) {
                case 1: goto __state1;
                case 2: goto __state2;
            }
            i = __this.count - 1;
        __loop:
            if (i < 0) goto __state2;
            __current = __this.items[i];
            __state = 1;
            return true;
        __state1:
            --i;
            goto __loop;
        __state2:
            __state = 2;
            return false;
        }

        public void Dispose() {
            __state = 2;
        }

        void IEnumerator.Reset() {
            throw new NotSupportedException();
        }
    }
}

В предыдущем преобразовании код в блоке итератора преобразуется в конечный автомат и помещается в метод MoveNext класса перечислителя.In the preceding translation, the code in the iterator block is turned into a state machine and placed in the MoveNext method of the enumerator class. Более того, локальная переменная i преобразуется в поле в объекте перечислителя, чтобы оно продолжало существовать между вызовами MoveNext.Furthermore, the local variable i is turned into a field in the enumerator object so it can continue to exist across invocations of MoveNext.

В следующем примере выводится простая таблица умножения целых чисел от 1 до 10.The following example prints a simple multiplication table of the integers 1 through 10. Метод FromTo в примере возвращает перечислимый объект и реализуется с помощью итератора.The FromTo method in the example returns an enumerable object and is implemented using an iterator.

using System;
using System.Collections.Generic;

class Test
{
    static IEnumerable<int> FromTo(int from, int to) {
        while (from <= to) yield return from++;
    }

    static void Main() {
        IEnumerable<int> e = FromTo(1, 10);
        foreach (int x in e) {
            foreach (int y in e) {
                Console.Write("{0,3} ", x * y);
            }
            Console.WriteLine();
        }
    }
}

Метод FromTo можно преобразовать в экземпляр созданного компилятором перечислимого класса, который инкапсулирует код в блоке итератора, как показано ниже.The FromTo method can be translated into an instantiation of a compiler-generated enumerable class that encapsulates the code in the iterator block, as shown in the following.

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

class Test
{
    ...

    static IEnumerable<int> FromTo(int from, int to) {
        return new __Enumerable1(from, to);
    }

    class __Enumerable1:
        IEnumerable<int>, IEnumerable,
        IEnumerator<int>, IEnumerator
    {
        int __state;
        int __current;
        int __from;
        int from;
        int to;
        int i;

        public __Enumerable1(int __from, int to) {
            this.__from = __from;
            this.to = to;
        }

        public IEnumerator<int> GetEnumerator() {
            __Enumerable1 result = this;
            if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
                result = new __Enumerable1(__from, to);
                result.__state = 1;
            }
            result.from = result.__from;
            return result;
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return (IEnumerator)GetEnumerator();
        }

        public int Current {
            get { return __current; }
        }

        object IEnumerator.Current {
            get { return __current; }
        }

        public bool MoveNext() {
            switch (__state) {
            case 1:
                if (from > to) goto case 2;
                __current = from++;
                __state = 1;
                return true;
            case 2:
                __state = 2;
                return false;
            default:
                throw new InvalidOperationException();
            }
        }

        public void Dispose() {
            __state = 2;
        }

        void IEnumerator.Reset() {
            throw new NotSupportedException();
        }
    }
}

Класс Enumerable реализует как перечислимые интерфейсы, так и интерфейсы перечислителя, что позволяет использовать в качестве перечислимого и перечислителя.The enumerable class implements both the enumerable interfaces and the enumerator interfaces, enabling it to serve as both an enumerable and an enumerator. При первом вызове метода GetEnumerator возвращается сам перечислимый объект.The first time the GetEnumerator method is invoked, the enumerable object itself is returned. Последующие вызовы GetEnumeratorперечислимого объекта, если таковые имеются, возвращают копию перечислимого объекта.Subsequent invocations of the enumerable object's GetEnumerator, if any, return a copy of the enumerable object. Таким же, каждый возвращаемый перечислитель имеет собственное состояние, и изменения в одном перечислителе не повлияют на другой.Thus, each returned enumerator has its own state and changes in one enumerator will not affect another. Метод Interlocked.CompareExchange используется для обеспечения поточно-ориентированной операции.The Interlocked.CompareExchange method is used to ensure thread-safe operation.

Параметры from и to включаются в поля класса Enumerable.The from and to parameters are turned into fields in the enumerable class. Поскольку from изменяется в блоке итератора, вводится дополнительное __from поле для хранения начального значения, присвоенного from в каждом перечислителе.Because from is modified in the iterator block, an additional __from field is introduced to hold the initial value given to from in each enumerator.

Метод MoveNext создает исключение InvalidOperationException, если оно вызывается, когда __state 0.The MoveNext method throws an InvalidOperationException if it is called when __state is 0. Это защищает от использования перечислимого объекта в качестве объекта перечислителя без предварительного вызова GetEnumerator.This protects against use of the enumerable object as an enumerator object without first calling GetEnumerator.

В следующем примере показан простой класс дерева.The following example shows a simple tree class. Класс Tree<T> реализует свой метод GetEnumerator с помощью итератора.The Tree<T> class implements its GetEnumerator method using an iterator. Итератор перечисляет элементы дерева в инфиксные порядке.The iterator enumerates the elements of the tree in infix order.

using System;
using System.Collections.Generic;

class Tree<T>: IEnumerable<T>
{
    T value;
    Tree<T> left;
    Tree<T> right;

    public Tree(T value, Tree<T> left, Tree<T> right) {
        this.value = value;
        this.left = left;
        this.right = right;
    }

    public IEnumerator<T> GetEnumerator() {
        if (left != null) foreach (T x in left) yield x;
        yield value;
        if (right != null) foreach (T x in right) yield x;
    }
}

class Program
{
    static Tree<T> MakeTree<T>(T[] items, int left, int right) {
        if (left > right) return null;
        int i = (left + right) / 2;
        return new Tree<T>(items[i], 
            MakeTree(items, left, i - 1),
            MakeTree(items, i + 1, right));
    }

    static Tree<T> MakeTree<T>(params T[] items) {
        return MakeTree(items, 0, items.Length - 1);
    }

    // The output of the program is:
    // 1 2 3 4 5 6 7 8 9
    // Mon Tue Wed Thu Fri Sat Sun

    static void Main() {
        Tree<int> ints = MakeTree(1, 2, 3, 4, 5, 6, 7, 8, 9);
        foreach (int i in ints) Console.Write("{0} ", i);
        Console.WriteLine();

        Tree<string> strings = MakeTree(
            "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
        foreach (string s in strings) Console.Write("{0} ", s);
        Console.WriteLine();
    }
}

Метод GetEnumerator можно преобразовать в экземпляр класса перечислителя, созданного компилятором, который инкапсулирует код в блоке итератора, как показано ниже.The GetEnumerator method can be translated into an instantiation of a compiler-generated enumerator class that encapsulates the code in the iterator block, as shown in the following.

class Tree<T>: IEnumerable<T>
{
    ...

    public IEnumerator<T> GetEnumerator() {
        return new __Enumerator1(this);
    }

    class __Enumerator1 : IEnumerator<T>, IEnumerator
    {
        Node<T> __this;
        IEnumerator<T> __left, __right;
        int __state;
        T __current;

        public __Enumerator1(Node<T> __this) {
            this.__this = __this;
        }

        public T Current {
            get { return __current; }
        }

        object IEnumerator.Current {
            get { return __current; }
        }

        public bool MoveNext() {
            try {
                switch (__state) {

                case 0:
                    __state = -1;
                    if (__this.left == null) goto __yield_value;
                    __left = __this.left.GetEnumerator();
                    goto case 1;

                case 1:
                    __state = -2;
                    if (!__left.MoveNext()) goto __left_dispose;
                    __current = __left.Current;
                    __state = 1;
                    return true;

                __left_dispose:
                    __state = -1;
                    __left.Dispose();

                __yield_value:
                    __current = __this.value;
                    __state = 2;
                    return true;

                case 2:
                    __state = -1;
                    if (__this.right == null) goto __end;
                    __right = __this.right.GetEnumerator();
                    goto case 3;

                case 3:
                    __state = -3;
                    if (!__right.MoveNext()) goto __right_dispose;
                    __current = __right.Current;
                    __state = 3;
                    return true;

                __right_dispose:
                    __state = -1;
                    __right.Dispose();

                __end:
                    __state = 4;
                    break;

                }
            }
            finally {
                if (__state < 0) Dispose();
            }
            return false;
        }

        public void Dispose() {
            try {
                switch (__state) {

                case 1:
                case -2:
                    __left.Dispose();
                    break;

                case 3:
                case -3:
                    __right.Dispose();
                    break;

                }
            }
            finally {
                __state = 4;
            }
        }

        void IEnumerator.Reset() {
            throw new NotSupportedException();
        }
    }
}

Созданный компилятором долгим сроком, используемый в инструкциях foreach, ликвидируется в полях __left и __right объекта перечислителя.The compiler generated temporaries used in the foreach statements are lifted into the __left and __right fields of the enumerator object. Поле __state объекта перечислителя тщательно обновляется, так что правильный метод Dispose() будет вызываться правильно при возникновении исключения.The __state field of the enumerator object is carefully updated so that the correct Dispose() method will be called correctly if an exception is thrown. Обратите внимание, что невозможно написать преобразованный код с помощью простых инструкций foreach.Note that it is not possible to write the translated code with simple foreach statements.

Асинхронные функцииAsync functions

Метод (методы) или анонимная функция (выражения анонимных функций) с модификатором async называется асинхронной функцией.A method (Methods) or anonymous function (Anonymous function expressions) with the async modifier is called an async function. Как правило, термин Async используется для описания любого вида функции, имеющей модификатор async.In general, the term async is used to describe any kind of function that has the async modifier.

Ошибка времени компиляции для списка формальных параметров асинхронной функции для указания любых ref или outных параметров.It is a compile-time error for the formal parameter list of an async function to specify any ref or out parameters.

Return_type асинхронного метода должен быть либо void, либо типом задачи.The return_type of an async method must be either void or a task type. Типы задач являются System.Threading.Tasks.Taskми и типами, созданными из System.Threading.Tasks.Task<T>.The task types are System.Threading.Tasks.Task and types constructed from System.Threading.Tasks.Task<T>. Для краткости в этой главе ссылки на эти типы указываются как Task и Task<T>соответственно.For the sake of brevity, in this chapter these types are referenced as Task and Task<T>, respectively. Асинхронный метод, возвращающий тип задачи, называется возвратом задачи.An async method returning a task type is said to be task-returning.

Точное определение типов задач определяется реализацией, но с точки зрения языка тип задачи находится в одном из состояний — неполные, выполненные или неисправные.The exact definition of the task types is implementation defined, but from the language's point of view a task type is in one of the states incomplete, succeeded or faulted. Сбойная задача записывает соответствующую ошибку.A faulted task records a pertinent exception. Успешный Task<T> записывает результат типа T.A succeeded Task<T> records a result of type T. Типы задач можно ожидать, поэтому они могут быть операндами выражений await (выражения await).Task types are awaitable, and can therefore be the operands of await expressions (Await expressions).

Асинхронный вызов функции имеет возможность приостановить вычисление с помощью выражений await (выражений await) в тексте.An async function invocation has the ability to suspend evaluation by means of await expressions (Await expressions) in its body. Ознакомительная версия может быть возобновлена в точке приостановки выражения await с помощью делегатавозобновления.Evaluation may later be resumed at the point of the suspending await expression by means of a resumption delegate. Делегат возобновления имеет тип System.Actionи при его вызове вычисление асинхронного вызова функции будет возобновлено из выражения await, в котором она была отключена.The resumption delegate is of type System.Action, and when it is invoked, evaluation of the async function invocation will resume from the await expression where it left off. Текущий вызывающий объект асинхронного вызова функции является исходным вызывающим объектом, если вызов функции никогда не был приостановлен, или последним вызывающим объектом делегата возобновления в противном случае.The current caller of an async function invocation is the original caller if the function invocation has never been suspended, or the most recent caller of the resumption delegate otherwise.

Вычисление асинхронной функции, возвращающей задачуEvaluation of a task-returning async function

Вызов асинхронной функции, возвращающей задачи, приводит к формированию экземпляра возвращаемого типа задачи.Invocation of a task-returning async function causes an instance of the returned task type to be generated. Это называется задачей возврата асинхронной функции.This is called the return task of the async function. Изначально задача находится в неполном состоянии.The task is initially in an incomplete state.

Текст асинхронной функции затем вычисляется до тех пор, пока он не будет приостановлен (путем достижения выражения await) или завершается, после чего управление точкой возвращается вызывающему объекту вместе с задачей возврата.The async function body is then evaluated until it is either suspended (by reaching an await expression) or terminates, at which point control is returned to the caller, along with the return task.

Когда тело асинхронной функции завершается, задача возврата перемещается из неполного состояния:When the body of the async function terminates, the return task is moved out of the incomplete state:

  • Если тело функции завершается в результате достижения оператора return или конца тела, все результирующие значения записываются в задачу возврата, которая переходит в состояние успеха.If the function body terminates as the result of reaching a return statement or the end of the body, any result value is recorded in the return task, which is put into a succeeded state.
  • Если тело функции завершается в результате неперехваченного исключения (оператор Throw), исключение записывается в задачу возврата, которая переносится в состояние сбоя.If the function body terminates as the result of an uncaught exception (The throw statement) the exception is recorded in the return task which is put into a faulted state.

Вычисление асинхронной функции, возвращающей значение voidEvaluation of a void-returning async function

Если тип возвращаемого значения асинхронной функции — void, вычисление отличается от приведенного выше следующим образом. Поскольку задача не возвращается, функция передает завершение и исключения в контекст синхронизациитекущего потока.If the return type of the async function is void, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread's synchronization context. Точное определение контекста синхронизации зависит от реализации, но представляет собой представление "где" текущий поток выполняется.The exact definition of synchronization context is implementation-dependent, but is a representation of "where" the current thread is running. Контекст синхронизации уведомляется при вычислении асинхронной функции, возвращающей нулевое значение, завершается успешно или вызывает исключение неперехваченных исключений.The synchronization context is notified when evaluation of a void-returning async function commences, completes successfully, or causes an uncaught exception to be thrown.

Это позволяет контексту отследить количество выполняемых асинхронных функций, возвращающих void, и решить, как распространять исключения, поступающие из них.This allows the context to keep track of how many void-returning async functions are running under it, and to decide how to propagate exceptions coming out of them.