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_declarationtype_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规范 (类 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_listA 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. 它指定类隐藏继承的成员相同的名称,如中所述的新修饰符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.

publicprotectedinternal,和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).

abstractsealedstatic修饰符将在以下各节中讨论。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引入了一个抽象方法Fthe abstract class A introduces an abstract method F. B引入了其他方法G,但由于它不提供的实现FB也必须声明为抽象。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. 由于没有抽象成员在CC是允许 (但不是必需的) 为非抽象。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:

  • 可能不包括静态类sealedabstract修饰符。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. 静态类隐式继承自类型objectA 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.
  • 静态类不能具有与成员protectedprotected 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 (Namespace 和类型名称) 允许以引用静态类,如果A namespace_or_type_name (Namespace and type names) is permitted to reference a static class if

  • Namespace_or_type_nameTnamespace_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_nameTtypeof_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.

Partial 修饰符Partial 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列表只有接口类型、 直接基类被假定为objectIf 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称为派生自Aclass A is said to be the direct base class of B, and B is said to be derived from A. 由于Adoes 未显式指定直接基类,其直接基类是隐式objectSince 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类进行派生privateinternal类。For example, it is a compile-time error for a public class to derive from a private or internal class.

类类型的直接基类必须不能是任何以下类型: System.ArraySystem.DelegateSystem.MulticastDelegateSystem.Enum,或System.ValueTypeThe 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暂时被假定为objectWhile 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,,因此 (通过的规则Namespace 和类型名称)C不被视为到有一个成员Bis 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. 以下示例中,基类的引用BAobjectReferring 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,和objectthe 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依赖于BWhen 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(其最近的封闭类),该循环依赖于Aresults 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是错误,因为它会尝试派生自sealedAclass 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. getset属性访问器中的令牌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值类型约束 structA primary constraint can be a class type or the reference type constraint class or the value type constraint struct. 辅助约束可以是type_parameterinterface_typeA 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 的结构类型、 枚举类型和值类型约束的类型参数满足此约束。All non-nullable struct types, enum types, and type parameters having the value type constraint satisfy this constraint. 请注意,尽管分类为值类型,可以为 null 的类型 (为 Null 的类型) 不满足的值类型约束。Note that although classified as a value type, a nullable type (Nullable types) does not satisfy the value type constraint. 具有值类型约束的类型形参不能同时具有constructor_constraintA 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.
  • 类型不能sealedThe type must not be sealed.
  • 类型不能属于以下类型之一: System.ArraySystem.DelegateSystem.Enum,或System.ValueTypeThe type must not be one of the following types: System.Array, System.Delegate, System.Enum, or System.ValueType.
  • 类型不能objectThe 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取决于 TIf a type parameter T is used as a constraint for type parameter S then S depends on T.
  • 如果类型形参S取决于类型参数TT取决于类型参数U然后S取决于 UIf 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.
  • 如果Sclass_type约束AT具有class_type约束B则必须是标识转换或隐式引用转换从AB或从隐式引用转换BAIf 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还取决于类型参数UU具有class_type约束AT具有class_type约束B则必须为标识转换或从隐式引用转换AB或从隐式引用转换BAIf 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.ObjectSystem.ValueTypeSystem.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没有主约束或类型参数约束,其有效的基类是objectIf T has no primary constraints or type parameter constraints, its effective base class is object.
  • 如果T具有值类型约束,其有效的基类是System.ValueTypeIf T has the value type constraint, its effective base class is System.ValueType.
  • 如果Tclass_type约束C但没有type_parameter约束,其有效的基类是CIf 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约束,其有效的基类是objectIf T has the reference type constraint but no class_type constraints, its effective base class is object.

在这些规则,如果 T 具有约束V也就是说value_type,改为使用的最具体的基类型Vclass_typeFor 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_typeThese 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.
  • 如果Tinterface_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约束,其有效的接口集是有效的接口集的并集及其得以参数约束。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.

类型参数是已知为引用类型如果它具有引用类型约束,或者不是其有效基类objectSystem.ValueTypeA 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被约束为始终实现IPrintablethe 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_declarationstruct_declarationinterface_declaration表示分部类型声明,如果它包含partial修饰符。A class_declaration, struct_declaration or interface_declaration represents a partial type declaration if it includes a partial modifier. partial 不是一个关键字,并仅充当一个修饰符,如果后面紧接着关键字之一classstructinterface在类型声明中,或在类型前面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

如果分部类型声明包含的可访问性规范 ( publicprotectedinternal,和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 {...}

基接口的类的一套CIAIB,和ICthe 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
{
    ...
}

成员Members

分部方法除外 (分部方法),在多个部分中声明类型的成员的集是只需在每个部分中声明成员的集的并集。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.

分部方法不能定义访问修饰符,但为隐式privatePartial 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_declaration从直接基类和成员继承。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 立即所包含的类_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. 此外,签名 (签名和超载) 的一种方法必须不同于同一类中声明的所有其他方法的签名和完全不同的签名不能在同一个类中声明的两种方法通过refoutIn 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.
  • 实例构造函数的签名必须不同于在同一类中声明所有其他实例构造函数的签名,并且完全由不同的签名不能在同一个类中声明的两个构造函数refoutThe 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中声明的成员以及AIf 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_argumentclass_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类型参数TIn 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> 还有类声明的继承的成员BD<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[]>. 然后,作为类型参数Bint[]替换为Upublic 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).

New 修饰符The 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可以具有下列任一五个的可能类型的声明可访问性 (声明可访问性): publicprotected internalprotectedinternalprivateA 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.ME必须表示一个类型,其中包含MWhen 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:

  • 当实例成员M中引用member_access (成员访问) 的窗体E.ME必须表示一个包含类型的一个实例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_nameThe 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

非嵌套类型可以具有publicinternal声明可访问性,而且有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:

  • 类中声明的嵌套的类型可以具有任何声明可访问性的五个窗体 (publicprotected internalprotectedinternal,或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.
  • 在结构中声明的嵌套的类型可以具有任何三种形式的声明可访问性 (publicinternal,或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 {...} }
}

声明的私有嵌套的类Nodedeclares 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中定义Baseshows 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,并将传递其自身thisNested的构造函数,提供对后续访问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

嵌套的类型有权访问它的包含类型,包括具有包含类型的成员可以访问的成员的所有privateprotected声明可访问性。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,其中包含嵌套的类Nestedshows 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,也可由的实例通过调用Derivedthe 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修饰符 (的新修饰符)。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:

  • 若要允许使用一个普通的标识符作为 get 方法名称或访问权限设置为 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.
  • 若要允许其他语言进行互操作作为 get 方法名称中使用一个普通的标识符或设置对 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_Pset_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,保留了以下签名: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修饰符 (的新修饰符),和有效组合的四种访问修饰符 (访问修饰符)。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_declarationThe 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.

类型常量中指定声明必须是sbytebyteshortushortintuintlongulongcharfloatdoubledecimalboolstring、 一个enum_type,或reference_typeThe 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_expressionA constant can itself participate in a constant_expression. 因此,可在任何需要的构造函数中使用常量constant_expressionThus, 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. 以来创建的非 null 值的唯一办法reference_type而不string是应用new运算符,并且new运算符中不允许使用constant_表达式,唯一可能的值的常量reference_type以外的其他 sstringnullSince 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_expressionreadonly字段 (只读字段) 可以改为使用。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,生成值1011,并12the 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. 上述示例中,如果引用AB声明,因此在单独程序中,将有可能A.X依赖B.Z,但B.Z无法则不能同时取决于A.YReferring 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_declarationThe 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 必须表示一个类型,其中包含的实例MWhen 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. Readonly 字段的直接赋值只能作为的一部分,该声明或在实例构造函数或同一个类中的静态构造函数中发生。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字段允许仅在以下上下文中:(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字段作为outref参数。These are also the only contexts in which it is valid to pass a readonly field as an out or ref parameter.

尝试将分配给readonly字段,或将其作为传递outref在任何其他上下文中的参数是一个编译时错误。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;
    }
}

BlackWhiteRedGreen,并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);
        }
    }
}

Program1Program2命名空间表示两个单独编译的程序。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. 可变写入具有"发布语义;"也就是说,它保证操作之后在对指令序列中的写入指令之前的任何内存引用。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_typeA reference_type.
  • 类型bytesbyteshortushortintuintcharfloatboolSystem.IntPtr,或System.UIntPtrThe type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr.
  • Enum_type具有枚举基类型的bytesbyteshortushortint,或uintAn 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可以启动一个新线程的运行方法Thread2In this example, the method Main starts a new thread that runs the method Thread2. 此方法将值存储到一个名为的非易失性字段result,然后将存储true可变字段中finishedThis method stores a value into a non-volatile field called result, then stores true in the volatile field finished. 主线程等待字段finished设置为true,然后读取字段resultThe main thread waits for the field finished to be set to true, then reads the field result. 由于finished已声明volatile,在主线程必须读取的值143字段中resultSince finished has been declared volatile, the main thread must read the value 143 from the field result. 如果该字段finished尚未声明volatile,则将允许到应用商店result后的存储为对主线程可见finished,,因此读取值的主线程0从字段resultIf 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

因为bi都被自动初始化为默认值。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发生时执行静态字段初始值设定项和分配到is实例字段初始值设定项执行时发生。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. 尽管的循环定义,而 b,该程序是有效。Despite the circular definitions of a and b, the program is valid. 这会导致输出It results in the output

a = 1, b = 2

因为静态字段ab初始化为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初始化为1When the initializer for a runs, the value of b is zero, and so a is initialized to 1. 时的初始值设定项b运行时,值a已经1,,因此b初始化为2When 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_nameThus, 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(的新修饰符), 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.
  • 声明包含最多一个的下列修饰符: staticvirtual,和overrideThe declaration includes at most one of the following modifiers: static, virtual, and override.
  • 声明包含最多一个的下列修饰符:newoverrideThe declaration includes at most one of the following modifiers: new and override.
  • 如果声明包含abstract修饰符,然后声明不包含任何下列修饰符: staticvirtualsealedexternIf the declaration includes the abstract modifier, then the declaration does not include any of the following modifiers: static, virtual, sealed or extern.
  • 如果声明包含private修饰符,然后声明不包含任何下列修饰符: virtualoverride,或abstractIf 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修饰符,则它不包含任何下列修饰符: newpublicprotectedinternalprivatevirtualsealedoverride``abstract,或externIf 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_typevoid如果方法不返回值。The return_type is void if the method does not return a value. 如果声明包含partial修饰符,则返回类型必须可voidIf 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_clause指定单个类型参数的约束 (类型参数约束),并且如果可能仅指定type_parameter_列表还提供,并且该方法没有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.

有关abstractextern方法, 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. 此外,一种方法的签名必须不同于同一类中声明的所有其他方法的签名并完全由不同的签名不能在同一个类中声明的两种方法refoutIn 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_parameters 处于范围内整个method_declaration,并可用于对整个该作用域中的窗体类型return_typemethod_body,并type_parameter_constraints_clauses 但不能在属性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_listThe 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_arrayThe formal parameter list consists of one or more comma-separated parameters of which only the last may be a parameter_array.

一个fixed_parameter包含一组可选特性(属性),一个可选refoutthis修饰符,类型,则标识符和可选default_argumentA 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_parameterdefault_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_listA required parameter may not appear after an optional parameter in a formal_parameter_list.

一个refout参数不能具有default_argumentA ref or out parameter cannot have a default_argument. 表达式default_argument必须是以下值之一:The expression in a default_argument must be one of the following:

  • a 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有关Mi是必需的 ref 参数,而d是必需的值参数,而bsot可选值的参数和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.

如中所述签名和超载,则refout修饰符是方法的签名的一部分,但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.

一种方法被允许将新值分配给 value 参数。A method is permitted to assign new values to a value parameter. 此类分配只会影响由值参数表示的本地存储位置 — 它们不会影响在方法调用中给出的实参。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.

在方法调用中的相应参数的形参时引用参数,必须包含关键字refvariable_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

调用SwapMainx表示iy表示jFor the invocation of Swap in Main, x represents i and y represents j. 因此,调用具有交换的值的效果ijThus, 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);
    }
}

在调用FG将传递到引用s两个abthe invocation of F in G passes a reference to s for both a and b. 因此,对于该调用,名称sa,并b所有引用相同的存储位置,并且所有三个赋值修改实例字段sThus, 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修饰符是一个 output 参数。A parameter declared with an out modifier is an output parameter. 与引用参数类似,输出参数不会创建新的存储位置。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.

输出参数的形参时,在方法调用中的相应参数必须包含关键字outvariable_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.

在方法中,就像本地变量时,输出参数最初被认为是未分配和使用它的值之前必须明确赋值。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

请注意,dirname传递到之前的变量可以是未分配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修饰符和修饰符refoutIt 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[]本身就是隐式转换为键入objectThe 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必须表示一个类型,其中包含的一个实例MWhen 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(其中RC或派生的类从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:

  • 首先,重载决策应用于CN,并A以选择特定方法M从一组的方法中声明并由继承CFirst, 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是虚拟方法的派生程度最高的实现MR调用。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,则此操作的派生程度最高的实现MIf R contains the introducing virtual declaration of M, then this is the most derived implementation of M.
  • 否则为如果R包含overrideM,则此操作的派生程度最高的实现MOtherwise, if R contains an override of M, then this is the most derived implementation of M.
  • 否则,大多数派生的实现M相对于R的派生程度最高实现相同M方面的直接基类ROtherwise, 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和虚拟方法GIn the example, A introduces a non-virtual method F and a virtual method G. 该类B引入了新的非虚拟方法F,从而隐藏继承F,并且还重写继承的方法GThe 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.GNotice 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();
    }
}

CD类包含两个具有相同的签名的虚拟方法:一个引起A引入的一个Cthe C and D classes contain two virtual methods with the same signature: The one introduced by A and the one introduced by C. 引入的方法C隐藏继承的方法AThe method introduced by C hides the method inherited from A. 因此,重写中的声明D重写方法,通过引入C,并不适用于D若要重写方法由引入AThus, 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作为在同一程序中声明和CFor 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.
  • 重写声明未指定类型参数约束子句。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方法中声明Athe 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)BHad 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中的方法Athe 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方法继承自Athe F method in B hides the virtual F method inherited from A. 因为新FB具有私有访问权限,其作用域仅包含的类的主体B并不会扩展到CSince the new F in B has private access, its scope only includes the class body of B and does not extend to C. 因此,声明FC允许重写F继承自ATherefore, 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进一步重写FB'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. 尽管一个抽象方法隐式也是一个虚拟方法,但它不能有修饰符virtualAlthough 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类定义本身就可以绘制一个几何形状对象的抽象概念。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. EllipseBox类是具体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.TaskThe 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>TOtherwise, 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; }.

当某个方法含有非空结果类型和一个块的正文,每个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. GH方法是否正确,因为所有可能的执行路径中指定一个返回值的 return 语句结束。The G and H methods are correct because all possible execution paths end in a return statement that specifies a return value. I方法是否正确,因为其主体等效于只是单个的返回语句中包含的语句块。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(的新修饰符), 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; } },并因此仅可指定仅定义了 getter属性的 getter 的结果由单个表达式。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. 因此,不能将属性作为refout参数。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 必须表示一个类型,其中包含的实例MWhen 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_declarationset_accessor_declaration,和 / 或。The accessor declarations consist of a get_accessor_declaration, a set_accessor_declaration, or both. 每个访问器声明包含令牌getset跟一个可选accessor_modifier和一个accessor_bodyEach 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仅当属性或索引器同时具有允许getset访问器,然后允许仅在其中一个访问器。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 internalinternalprotected,或privateIf 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可能是internalprotected,或privateIf the property or indexer has a declared accessibility of protected internal, the accessor_modifier may be either internal, protected, or private.
    • 如果属性或索引器已声明可访问性internalprotected,则accessor_modifier必须是privateIf 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.

有关abstractextern属性, 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访问器始终命名为valueThe 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.

基于是否存在或缺少getset访问器属性分类,如下所示: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属性将返回字符串存储在私有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.

getset属性访问器不是不同的成员,并且不能单独声明属性访问器。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中的属性Athe 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. 但请注意,强制转换可用于访问隐藏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字段中,xy,以存储其位置。Here, the Label class uses two int fields, x and y, to store its location. 公开的位置是公开既用作X和一个Y属性和 asLocation类型的属性PointThe 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; }
    }
}

xy改为已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类包含三个属性InOut,和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. 但如果应用程序不引用InError为这些设备创建属性,则任何对象。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字段中,仅定义了 getter 的自动属性还中可以分配到封闭类的构造函数的正文。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; }
}

请注意,分配到只读字段合法的因为它们构造函数内发生。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_modifierAn 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 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修饰符将应用于这两个访问器的读写属性,不可能只有一个访问器的读写属性必须是虚拟的。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.

属性声明,包括这两个abstractoverride修饰符指定该属性是抽象的重写基属性。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; }
    }
}

此处的声明XY,和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访问器的Xset访问器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(的新修饰符), 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_declarationsAn event declaration may include event_accessor_declarations. 但是,如果不是,请为非 extern 非抽象事件,编译器会自动提供它们 (类似字段的事件); 对于外部事件,从外部提供访问器。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_declarators。An event declaration that omits event_accessor_declarations defines one or more events—one for each of the variable_declarators. 特性和修饰符应用于所有通过此类声明的成员event_declarationThe attributes and modifiers apply to all of the members declared by such an event_declaration.

它是编译时错误event_declaration包含这两abstract修饰符,大括号分隔event_accessor_declarationsIt 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_declarationsBecause 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的使用的事件声明abstractexternal修饰符以包括variable_initializerIt 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 += yx -= 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. 若要使用这种方式,事件不能abstractextern,并且必须显式包括event_accessor_declarationsTo 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. 如果尚未添加任何事件处理程序,该字段包含nullIf 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_declarationThe accessor declarations consist of an add_accessor_declaration and a remove_accessor_declaration. 每个访问器声明包含令牌addremoveEach 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_declarationremove_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. 事件访问器的隐式参数名为valueThe 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_declarationremove_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 必须表示一个类型,其中包含的实例MWhen 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_declarationsBecause an abstract event declaration provides no actual implementation, it cannot provide brace-delimited event_accessor_declarations.

事件声明同时包含abstractoverride修饰符指定该事件是抽象的重写基事件。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(的新修饰符), 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.

索引器声明可能会有所与方法声明相同的规则 (方法) 方面的修饰符的有效组合,有一个例外是,静态修饰符不允许在索引器声明。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.

修饰符virtualoverride,和abstract是一种情况下除外互相排斥。The modifiers virtual, override, and abstract are mutually exclusive except in one case. abstractoverride修饰符可能以便抽象索引器可以重写虚拟一起使用。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. 索引器是显式接口成员的实现,除非类型跟关键字thisUnless the indexer is an explicit interface member implementation, the type is followed by the keyword this. 有关显式接口成员实现,类型后跟interface_type、".",和关键字thisFor 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. 索引器的形参列表对应于一种方法 (方法参数),但必须指定至少一个参数,并且refout不允许参数修饰符.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; } },并因此仅可指定仅定义了 getter 的索引器的 getter 的结果由单个表达式给定。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. 因此,不能作为索引器元素传递refout参数。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索引器访问器对应于具有索引器,以及一个附加参数相同的形参列表的方法名为valueA 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). 它是在运算符声明中指定的编译时错误refout参数。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:

  • 一元+-!,或~运算符必须采用一个参数的类型TT?和可以返回任何类型。A unary +, -, !, or ~ operator must take a single parameter of type T or T? and can return any type.
  • 一元++--运算符必须将单个类型的参数TT?并且必须返回相同类型派生自它。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.
  • 一元truefalse运算符必须将单个类型的参数TT?,并且必须返回类型boolA 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.

truefalse一元运算符要求成对的声明。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. truefalse运算符是中作了进一步介绍用户定义的条件逻辑运算符布尔表达式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:

  • 二进制非移位运算符必须采用两个参数,在至少一个必须具有类型TT?,并且可以返回任何类型。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.
  • 二进制<<>>运算符必须将两个参数,其中第一个类型必须TT?,其中第二个必须具有类型intint?,并且可以返回任何类型。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,如果ST是可以为 null 的类型,让S0T0指其基础类型,否则S0T0是等于ST分别。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:

  • S0T0是不同的类型。S0 and T0 are different types.
  • 要么S0T0是在运算符声明发生的类或结构类型。Either S0 or T0 is the class or struct type in which the operator declaration takes place.
  • 既不S0也不T0interface_typeNeither S0 nor T0 is an interface_type.
  • 除用户定义的转换,转换不存在从ST或从TSExcluding user-defined conversions, a conversion does not exist from S to T or from T to S.

出于这些规则,任何类型与关联的参数ST被视为具有不与其他类型的继承关系和参数将被忽略这些类型的任何约束的唯一类型。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,Tintstring分别被视为与任何关系的唯一类型。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定义的转换从Cint并从intC,但不能从intboolFor 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,所有用户都定义的转换 (隐式或显式) 从ST将被忽略。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.
  • 如果预定义的显式转换 (显式转换) 存在从类型S键入T,从任何用户定义的显式转换ST将被忽略。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是一个接口类型,从用户定义的隐式转换ST将被忽略。If T is an interface type, user-defined implicit conversions from S to T are ignored.

否则为用户定义的隐式转换从ST仍被视为。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_typeIn 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. (请注意这是在签名中参与的返回类型的成员的唯一形式。)implicitexplicit分类的转换运算符不是运算符的签名的一部分。(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);
    }
}

从转换Digitbyte因为它永远不会引发异常或丢失信息,但从转换是隐式byteDigit是因为显式Digit只能表示可能的子集值bytethe 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_bodyAll 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_initializer的 s在其类中声明的实例字段。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);
    }
}

new B()用于创建实例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_bodyIt 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;
    }
}

包含多个变量的初始值设定项;它还包含两个窗体的构造函数初始值设定项 (basethis)。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. 否则,默认构造函数声明可访问性是公共的。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直接创建的实例TWhen 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. 因此它会声明一个空的私有实例构造函数。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.Fbecause 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.

析构函数通过重写虚拟方法来实现FinalizeSystem.ObjectDestructors 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
}

有效,并显示隐藏的方法System.ObjectFinalize方法。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.

IteratorsIterators

函数成员 (函数成员) 使用迭代器块实现 () 称为迭代器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_bodyoperator_bodyaccessor_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.

使用迭代器块实现函数成员时,它是形参列表的函数成员指定任何的编译时错误refout参数。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>. 为了简洁起见,在这一章中这些接口称为IEnumeratorIEnumerator<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>. 为了简洁起见,在这一章中这些接口称为IEnumerableIEnumerable<T>分别。For the sake of brevity, in this chapter these interfaces are referenced as IEnumerable and IEnumerable<T>, respectively.

Yield 类型Yield type

一个迭代器,将生成一系列值,所有相同的类型。An iterator produces a sequence of values, all of the same type. 这种类型称为产生类型的迭代器。This type is called the yield type of the iterator.

  • 返回一个迭代器的 yield 类型IEnumeratorIEnumerableobjectThe yield type of an iterator that returns IEnumerator or IEnumerable is object.
  • 返回一个迭代器的 yield 类型IEnumerator<T>IEnumerable<T>TThe 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:

  • 它实现IEnumeratorIEnumerator<T>,其中T是迭代器的 yield 类型。It implements IEnumerator and IEnumerator<T>, where T is the yield type of the iterator.
  • 它实现 System.IDisposableIt implements System.IDisposable.
  • (如果有) 的参数值的副本初始化和实例值传递给函数成员。It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
  • 它有四个可能的状态,之前运行挂起,以及,并最初处于之前状态。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.

以下各节介绍的具体行为MoveNextCurrent,并Dispose的成员IEnumerableIEnumerable<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.

MoveNext 方法The 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返回falseIf 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.
    • 枚举器对象的状态将变为挂起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.
    • 枚举器对象的状态将变为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:
    • 枚举器对象的状态将变为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.
    • 枚举器对象的状态将变为The state of the enumerator object is changed to after.
    • 异常传播的调用方继续MoveNext方法。The exception propagation continues to the caller of the MoveNext method.

Current 属性The Current property

枚举器对象的Current属性受yield return迭代器块中的语句。An enumerator object's Current property is affected by yield return statements in the iterator block.

当枚举器对象处于挂起状态的值Current是通过以前调用设置的值MoveNextWhen an enumerator object is in the suspended state, the value of Current is the value set by the previous call to MoveNext. 当枚举器对象处于之前运行,或状态的访问结果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>实现并将结果转换为objectFor 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.

Dispose 方法The Dispose method

Dispose方法用于通过引入枚举器对象来清理迭代状态。The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.

  • 如果枚举数对象的状态为之前,则调用Dispose将状态更改为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.
    • 执行任何最后块就像上次执行的yield return语句已yield break语句。Executes any finally blocks as if the last executed yield return statement were a yield break statement. 如果此操作导致引发和传播出去迭代器体引发异常,枚举器对象的状态设置为的调用方传播异常和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.
    • 将状态更改为Changes the state to 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方法返回在迭代器块中,指定枚举器对象封装代码和执行的迭代器块中的代码发生时枚举器对象的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:

  • 它实现IEnumerableIEnumerable<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. 具体而言,一个可枚举对象,还可以实施IEnumeratorIEnumerator<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.

GetEnumerator 方法The GetEnumerator method

一个可枚举对象提供的实现GetEnumerator的方法IEnumerableIEnumerable<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. 此处所述的实现基于 Microsoft 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以便它可以继续存在于调用到枚举器对象中的字段启用MoveNextFurthermore, 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.

fromto参数将转换为可枚举类中的字段。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如果它时,将调用__state0The MoveNext method throws an InvalidOperationException if it is called when __state is 0. 这可避免使用的可枚举对象的枚举器对象,而无需第一个调用作为GetEnumeratorThis 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修饰符。In general, the term async is used to describe any kind of function that has the async modifier.

它是指定任何异步函数的形参列表的编译时错误refout参数。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>. 为了简洁起见,在这一章中引用这些类型作为TaskTask<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>记录类型的结果TA 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:

  • 如果在函数体终止作为到达返回语句或正文的末尾的结果,返回 task,放入成功状态中记录任何结果值。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.

返回 void 的异步函数的求值Evaluation 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. 同步上下文的精确定义取决于实现,但是表示形式的当前线程正在运行"where"。The exact definition of synchronization context is implementation-dependent, but is a representation of "where" the current thread is running. 返回 void 的异步函数的求值开始、 成功完成,或会导致引发未捕获的异常时,通知同步上下文。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.