Classes

类是一种数据结构,它可能包含 (常量和字段的数据成员) 、函数成员 (方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数) 以及嵌套类型。A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, destructors and static constructors), and nested types. 类类型支持继承,这是一个派生类可以扩展和专用化基类的机制。Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class.

类声明Class declarations

Class_declaration 是声明新类 (类型声明) type_declarationA 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_modifier s (类修饰符) ,后跟一个可选 partial 修饰符,后跟关键字 class 和命名类的 标识符,后跟一个可选的 type_parameter_list (类型参数) ,后跟一个可选的 class_base 规范 (类基规范) ,后跟一个可选的 type_parameter_constraints_clause (类型参数约束集) ,后 跟 class_body (类体) ,可选择后跟一个分号。A class_declaration consists of an optional set of attributes (Attributes), followed by an optional set of class_modifier s (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_clause s (Type parameter constraints), followed by a class_body (Class body), optionally followed by a semicolon.

类声明无法提供 type_parameter_constraints_clause s,除非它还提供了 type_parameter_listA class declaration cannot supply type_parameter_constraints_clause s 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.

public、、 protected internalprivate 修饰符控制类的可访问性。The public, protected, internal, and private modifiers control the accessibility of the class. 根据出现类声明的上下文,某些修饰符可能不允许 (声明的可访问性) 。Depending on the context in which the class declaration occurs, some of these modifiers may not be permitted (Declared accessibility).

abstract sealed static 以下各节讨论了、和修饰符。The abstract, sealed and static modifiers are discussed in the following sections.

抽象类Abstract classes

abstract修饰符用于指示某个类不完整,并且它将仅用作一个基类。The abstract modifier is used to indicate that a class is incomplete and that it is intended to be used only as a base class. 抽象类与非抽象类在以下方面有所不同:An abstract class differs from a non-abstract class in the following ways:

  • 抽象类不能直接实例化,而是在抽象类上使用运算符的编译时错误 newAn 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 ,但由于它不提供的实现 F ,因此 B 还必须声明为抽象。Class B introduces an additional method G, but since it doesn't provide an implementation of F, B must also be declared abstract. 类将 C 重写 F 并提供实际实现。Class C overrides F and provides an actual implementation. 由于中没有抽象成员,因此 C C (允许但不需要) 为非抽象。Since there are no abstract members in C, C is permitted (but not required) to be non-abstract.

密封类Sealed classes

sealed修饰符用于阻止从类派生。The sealed modifier is used to prevent derivation from a class. 如果将密封类指定为其他类的基类,则会发生编译时错误。A compile-time error occurs if a sealed class is specified as the base class of another class.

密封类不能也是抽象类。A sealed class cannot also be an abstract class.

sealed修饰符主要用于防止意外派生,但它还支持某些运行时优化。The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain run-time optimizations. 特别是,由于知道密封类绝不会有任何派生类,因此可以将密封类实例上的虚函数成员调用转换为非虚拟调用。In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.

静态类Static classes

static修饰符用于标记声明为 静态类 的类。The static modifier is used to mark the class being declared as a static class. 静态类不能被实例化,不能用作类型并且只能包含静态成员。A static class cannot be instantiated, cannot be used as a type and can contain only static members. 只有静态类可以包含扩展方法的声明 (扩展方法) 。Only a static class can contain declarations of extension methods (Extension methods).

静态类声明受到下列限制:A static class declaration is subject to the following restrictions:

  • 静态类不能包含 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 (命名空间和类型名称, 则允许) 引用静态类A namespace_or_type_name (Namespace and type names) is permitted to reference a static class if

  • Namespace_or_type_nameT 窗体的 namespace_or_type_name 中的 T.I ,或The namespace_or_type_name is the T in a namespace_or_type_name of the form T.I, or
  • Namespace_or_type_name Ttypeof_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 直接基类 BB 被称为派生自 Aclass A is said to be the direct base class of B, and B is said to be derived from A. 由于 A 未显式指定直接基类,因此它的直接基类是隐式的 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 类从或类派生的编译时错误 private internalFor example, it is a compile-time error for a public class to derive from a private or internal class.

类类型的直接基类不得为以下任何类型: System.ArraySystem.DelegateSystem.MulticastDelegateSystem.EnumSystem.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 ,因此,) 的 命名空间和类型名称 的规则 ( 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[]>> Aobjectthe 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 既不是) 的基类,也不是的封闭类 AB depends on A (because A is both its direct base class and its immediately enclosing class), but A does not depend on B (since B is neither a base class nor an enclosing class of A). 因此,该示例是有效的。Thus, the example is valid.

不能从 sealed 类派生。It is not possible to derive from a sealed class. 示例中In the example

sealed class A {}

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

B 出错,因为它尝试从 sealed 类派生 Aclass 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_clause 来指定类型参数约束。Generic type and method declarations can optionally specify type parameter constraints by including type_parameter_constraints_clause s.

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

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

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

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

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

每个 type_parameter_constraints_clause 都包含标记 where ,后跟类型参数的名称,后跟一个冒号和该类型参数的约束列表。Each type_parameter_constraints_clause consists of the token where, followed by the name of a type parameter, followed by a colon and the list of constraints for that type parameter. 每个类型参数最多只能有一个 where 子句, where 子句可以按任意顺序列出。There can be at most one where clause for each type parameter, and the where clauses can be listed in any order. get set 属性访问器中的和标记一样,该 where 标记不是关键字。Like the get and set tokens in a property accessor, the where token is not a keyword.

子句中给定的约束列表 where 可以包括以下任何组件,顺序如下:单个主约束、一个或多个辅助约束以及构造函数约束 new()The list of constraints given in a where clause can include any of the following components, in this order: a single primary constraint, one or more secondary constraints, and the constructor constraint, new().

主约束可以是类类型或 引用类型约束 _ class 或 _值类型约束*_ structA primary constraint can be a class type or the reference type constraint _ class or the _value type constraint*_ struct. 辅助约束可以是 _type_parameter * 或 interface_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.EnumSystem.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.
  • 在给定子句中不能多次指定类型 whereA 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.
  • 在给定子句中不能多次指定类型 whereA 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 类型参数的约束, SS 依赖于 TIf a type parameter T is used as a constraint for type parameter S then S depends on T.
  • 如果类型参数 S 依赖于类型参数 T 并且 T 依赖于类型参数, US 依赖于 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.
  • 如果 S 具有 class_type 约束 A ,并且 T 具有 class_type 约束, B 则必须存在从到的标识转换或隐式 A B B A 引用转换。If S has a class_type constraint A and T has a class_type constraint B then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.
  • 如果 S 还依赖于类型参数 U 并且 U 具有 class_type 约束 A ,并且 T 具有 class_type 约束, B 则必须存在从到的标识转换或隐式 A B B A 引用转换。If S also depends on type parameter U and U has a class_type constraint A and T has a class_type constraint B then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.

S具有值类型约束并 T 具有引用类型约束的有效方法。It is valid for S to have the value type constraint and T to have the reference type constraint. T对类型 System.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.
  • 如果 T 具有 class_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 具有 value_type 的约束,则 V 改用的最特定基类型 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.
  • 如果 T 具有 interface_type 约束但没有 type_parameter 约束,则其有效接口集是其 interface_type 约束的集合。If T has interface_type constraints but no type_parameter constraints, its effective interface set is its set of interface_type constraints.
  • 如果 T 没有 interface_type 约束但具有 type_parameter 约束,则其有效接口集是其 type_parameter 约束的有效接口集的并集。If T has no interface_type constraints but has type_parameter constraints, its effective interface set is the union of the effective interface sets of its type_parameter constraints.
  • 如果 T 同时具有 interface_type 约束和 type_parameter 约束,则其有效接口集是其 interface_type 约束集的并集以及其 type_parameter 约束的有效接口集的并集。If T has both interface_type constraints and type_parameter constraints, its effective interface set is the union of its set of interface_type constraints and the effective interface sets of its type_parameter constraints.

如果类型参数具有引用类型约束,或者其有效的基类不是或,则已知该类型参数是 引用类型 object System.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 表示分部类型声明 partialA class_declaration, struct_declaration or interface_declaration represents a partial type declaration if it includes a partial modifier. partial 不是关键字,并且仅当它紧靠在某个关键字之前 classstruct interface 类型声明中或 void 方法声明中的类型之前出现时,才作为修饰符。partial is not a keyword, and only acts as a modifier if it appears immediately before one of the keywords class, struct or interface in a type declaration, or before the type void in a method declaration. 在其他上下文中,可以将其用作常规标识符。In other contexts it can be used as a normal identifier.

分部类型声明的每个部分都必须包含 partial 修饰符。Each part of a partial type declaration must include a partial modifier. 它必须具有相同的名称,并在与其他部分相同的命名空间或类型声明中声明。It must have the same name and be declared in the same namespace or type declaration as the other parts. partial修饰符指示类型声明的其他部分可能存在于其他位置,但不要求存在此类附加部分; 对于具有单个声明的类型,此类型对于包含修饰符的类型是有效的 partialThe 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.

可以通过使用修饰符在多个部分中声明嵌套类型 partialNested types may be declared in multiple parts by using the partial modifier. 通常,包含类型是使用声明的 partial ,并且嵌套类型的每个部分都是在包含类型的不同部分中声明的。Typically, the containing type is declared using partial as well, and each part of the nested type is declared in a different part of the containing type.

partial委托或枚举声明上不允许使用修饰符。The partial modifier is not permitted on delegate or enum declarations.

属性Attributes

分部类型的属性是通过将每个部件的属性按未指定顺序组合来确定的。The attributes of a partial type are determined by combining, in an unspecified order, the attributes of each of the parts. 如果特性放置在多个部件上,则等效于在类型上多次指定属性。If an attribute is placed on multiple parts, it is equivalent to specifying the attribute multiple times on the type. 例如,这两个部分:For example, the two parts:

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

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

等效于如下所示的声明:are equivalent to a declaration such as:

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

类型参数上的属性以类似的方式进行组合。Attributes on type parameters combine in a similar fashion.

修饰符Modifiers

当分部类型声明包含 (、、和修饰符的辅助功能规范时 public protected internal private) 它必须与包括辅助功能规范的所有其他部分协商。When a partial type declaration includes an accessibility specification (the public, protected, internal, and private modifiers) it must agree with all other parts that include an accessibility specification. 如果分部类型的任何部分都不包括辅助功能规范,则会为该类型提供适当的默认辅助功能 (声明的可访问性) 。If no part of a partial type includes an accessibility specification, the type is given the appropriate default accessibility (Declared accessibility).

如果嵌套类型的一个或多个分部声明包含 new 修饰符,则如果嵌套类型隐藏继承成员 (通过继承) 隐藏 该成员,则不会报告警告。If one or more partial declarations of a nested type include a new modifier, no warning is reported if the nested type hides an inherited member (Hiding through inheritance).

如果类的一个或多个分部声明包含 abstract 修饰符,则将类视为抽象类 (抽象类) 。If one or more partial declarations of a class include an abstract modifier, the class is considered abstract (Abstract classes). 否则,类被视为非抽象类。Otherwise, the class is considered non-abstract.

如果某个类的一个或多个分部声明包含 sealed 修饰符,则该类被视为密封 (密封类) 。If one or more partial declarations of a class include a sealed modifier, the class is considered sealed (Sealed classes). 否则,类被视为未密封。Otherwise, the class is considered unsealed.

请注意,类不能既是抽象的又是密封的。Note that a class cannot be both abstract and sealed.

unsafe 修饰符用于分部类型声明时,只有该特定部分被视为不安全的上下文 (不安全 上下文) 。When the unsafe modifier is used on a partial type declaration, only that particular part is considered an unsafe context (Unsafe contexts).

类型参数和约束Type parameters and constraints

如果泛型类型在多个部分中声明,则每个部分都必须声明类型参数。If a generic type is declared in multiple parts, each part must state the type parameters. 每个部分必须具有相同数量的类型参数,并且每个类型参数的名称必须相同。Each part must have the same number of type parameters, and the same name for each type parameter, in order.

当部分泛型类型声明包括) (子句约束时 where ,约束必须与包含约束的所有其他部分一致。When a partial generic type declaration includes constraints (where clauses), the constraints must agree with all other parts that include constraints. 具体而言,包括约束的每个部分都必须具有对同一组类型参数的约束,并且对于每个类型参数,主要、次要和构造函数约束的集必须是等效的。Specifically, each part that includes constraints must have constraints for the same set of type parameters, and for each type parameter the sets of primary, secondary, and constructor constraints must be equivalent. 如果两组约束包含相同的成员,则它们是等效的。Two sets of constraints are equivalent if they contain the same members. 如果部分泛型类型的任何部分都不指定类型参数约束,则类型参数被视为不受约束。If no part of a partial generic type specifies type parameter constraints, the type parameters are considered unconstrained.

示例The example

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

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

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

是正确的,因为包含约束的那些部分 (第一个) 有效地为同一组类型参数指定相同的一组主、辅助和构造函数约束。is correct because those parts that include constraints (the first two) effectively specify the same set of primary, secondary, and constructor constraints for the same set of type parameters, respectively.

基类Base class

当分部类声明包含基类规范时,它必须与包含基类规范的所有其他部分协商。When a partial class declaration includes a base class specification it must agree with all other parts that include a base class specification. 如果分部类的任何部分都不包含基类规范,则基类会成为 System.Object) (基类If no part of a partial class includes a base class specification, the base class becomes System.Object (Base classes).

基接口Base interfaces

在多个部分中声明的类型的基接口集是在每个部件上指定的基本接口的联合。The set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. 特定基接口在每个部分只能命名一次,但允许多个部件命名 () 的相同基接口。A particular base interface may only be named once on each part, but it is permitted for multiple parts to name the same base interface(s). 任何给定基接口的成员只能有一个实现。There must only be one implementation of the members of any given base interface.

示例中In the example

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

partial class C: IC {...}

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

类的基接口集 CIAIBICthe 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. 在该类型的多个部分中声明同一成员是编译时错误,除非该成员是带有修饰符的类型 partialIt 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 _. 如果主体被指定为 _block *,则声明为 实现分部方法声明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 可以为每个部件提供不同的指令 (使用) 。Thus, different using directives (Using directives) may be present for each part. 当在一个部分中解释简单名称 (类型推理) 时,只 using 考虑 () s 的命名空间声明的指令。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_declaration s 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_declaration 立即包含的 class_member_declaration 会将新成员引入此声明空间。A class_declaration creates a new declaration space (Declarations), and the class_member_declaration s immediately contained by the class_declaration introduce new members into this declaration space. 以下规则适用于 class_member_declarationThe following rules apply to class_member_declaration s:

  • 实例构造函数、析构函数和静态构造函数必须具有与直接封闭类相同的名称。Instance constructors, destructors and static constructors must have the same name as the immediately enclosing class. 所有其他成员的名称必须不同于立即封闭的类的名称。All other members must have names that differ from the name of the immediately enclosing class.
  • 常数、字段、属性、事件或类型的名称必须不同于同一个类中声明的所有其他成员的名称。The name of a constant, field, property, event, or type must differ from the names of all other members declared in the same class.
  • 方法的名称必须不同于同一个类中声明的所有其他非方法的名称。The name of a method must differ from the names of all other non-methods declared in the same class. 此外, (签名和方法的 重载) 的签名必须不同于同一类中声明的所有其他方法的签名,同一类中声明的两个方法的签名可能不只是 ref 和不同 outIn 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 来转换 class_base 规范的相应 type_argumentThese 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[]>. 然后,将替换为中的类型自变量,从而 B int[] U public U F(long index) 生成继承成员 public int[] F(long index)Then, as a type argument to B, int[] is substituted for U in public U F(long index), yielding the inherited member public int[] F(long index).

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. 通过删除修饰符,可取消显示此警告 newThis 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. 表示实例的编译时错误 EIt 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 必须表示包含的类型的实例 MWhen an instance member M is referenced in a member_access (Member access) of the form E.M, E must denote an instance of a type containing M. 它是的绑定时错误,它 E 表示类型。It is a binding-time error for E to denote a type.
  • 类的每个实例都包含该类的所有实例字段的单独集。Every instance of a class contains a separate set of all instance fields of the class.
  • 实例函数成员 (方法、属性、索引器、实例构造函数或析构函数) 对类的给定实例进行操作,并且可以将此实例作为 this (此访问) 的方式进行访问。An instance function member (method, property, indexer, instance constructor, or destructor) operates on a given instance of the class, and this instance can be accessed as this (This access).

下面的示例演示用于访问静态成员和实例成员的规则:The following example illustrates the rules for accessing static and instance members:

class Test
{
    int x;
    static int y;

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

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

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

F方法显示在实例函数成员中, Simple_name (简单名称) 可用于访问实例成员和静态成员。The F method shows that in an instance function member, a simple_name (Simple names) can be used to access both instance members and static members. G方法表明,在静态函数成员中,通过 simple_name 访问实例成员是编译时错误。The G method shows that in a static function member, it is a compile-time error to access an instance member through a simple_name. Main方法表明,在 Member_access (成员访问) ,必须通过实例访问实例成员,并且必须通过类型访问静态成员。The Main method shows that in a member_access (Member access), instance members must be accessed through instances, and static members must be accessed through types.

嵌套类型Nested types

在类或结构声明中声明的类型称为 *嵌套类型 _。A type declared within a class or struct declaration is called a *nested type _. 在编译单元或命名空间内声明的类型称为 _ 非嵌套类型 *。A type that is declared within a compilation unit or namespace is called a _*non-nested type**.

示例中In the example

using System;

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

B 是嵌套类型,因为它是在类中声明的 A ,而类 A 是非嵌套类型,因为它是在编译单元中声明的。class B is a nested type because it is declared within class A, and class A is a non-nested type because it is declared within a compilation unit.

完全限定的名称Fully qualified name

嵌套 类型 (完全限定名) 的完全限定名是 S.N ,其中 S 是声明类型的类型的完全限定名称 NThe 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.
  • 在结构中声明的嵌套类型可以有三种形式的声明的可访问性 (publicinternalprivate) ,而与其他结构成员一样,默认为已 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 ,并将其自己 this Nested 的构造函数传递给的构造函数,以便提供对 C 实例成员的后续访问。An instance of C creates an instance of Nested and passes its own this to Nested's constructor in order to provide subsequent access to C's instance members.

访问包含类型的私有和受保护成员Access to private and protected members of the containing type

嵌套类型可以访问其包含类型可以访问的所有成员,包括具有 private 和已 protected 声明可访问性的包含类型的成员。A nested type has access to all of the members that are accessible to its containing type, including members of the containing type that have private and protected declared accessibility. 示例The example

using System;

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

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

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

显示 C 包含嵌套类的类 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:

  • 如果为,则允许基础实现使用普通标识符作为方法名称,以获取或设置对 c # 语言功能的访问权限。To allow the underlying implementation to use an ordinary identifier as a method name for get or set access to the C# language feature.
  • 允许其他语言使用普通标识符作为方法名称,以获取或设置 c # 语言功能的访问权限。To allow other languages to interoperate using an ordinary identifier as a method name for get or set access to the C# language feature.
  • 为了帮助确保由一个符合的编译器接受的源接受另一个,通过使保留成员名称的细节在所有 c # 实现中保持一致。To help ensure that the source accepted by one conforming compiler is accepted by another, by making the specifics of reserved member names consistent across all C# implementations.

析构函数的声明 (析构 函数) 还会导致在为 析构函数) 保留 (成员名称 时保留签名。The declaration of a destructor (Destructors) also causes a signature to be reserved (Member names reserved for destructors).

为属性保留的成员名称Member names reserved for properties

对于属性 P (属性) 类型 T ,将保留以下签名:For a property P (Properties) of type T, the following signatures are reserved:

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

即使属性是只读的或只写的,都保留两个签名。Both signatures are reserved, even if the property is read-only or write-only.

示例中In the example

using System;

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

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

    new public void set_P(int value) {
    }
}

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

A 定义一个只读属性 P ,因此保留了和方法的签名 get_P set_Pa 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_declaration 声明的所有成员。The attributes and modifiers apply to all of the members declared by the constant_declaration. 即使常量被视为静态成员, constant_declaration 也既不需要也不允许 static 修饰符。Even though constants are considered static members, a constant_declaration neither requires nor allows a static modifier. 同一修饰符在常数声明中多次出现是错误的。It is an error for the same modifier to appear multiple times in a constant declaration.

Constant_declaration类型 指定声明引入的成员类型。The type of a constant_declaration specifies the type of the members introduced by the declaration. 该类型后跟一个 constant_declarator s 列表,其中每个都引入一个新成员。The type is followed by a list of constant_declarator s, 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.

在常量声明中指定的 类型 必须是 sbytebyte 、、、、、、、、、、、、、 short ushort int uint long ulong char float double decimal bool string enum_typereference_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_expression 的任何构造中都可以使用常量。Thus, a constant may be used in any construct that requires a constant_expression. 此类构造的示例包括 case 标签、 goto case 语句、 enum 成员声明、特性和其他常量声明。Examples of such constructs include case labels, goto case statements, enum member declarations, attributes, and other constant declarations.

常数表达式中所述, constant_expression 是可在编译时完全计算的表达式。As described in Constant expressions, a constant_expression is an expression that can be fully evaluated at compile-time. 由于不允许使用以外的 reference_type 的非 null 值的唯一方法 string 是应用 new 运算符,因此 new ,由于不允许在 constant_expression 中使用运算符,因此除外, reference_type 的常量的唯一可能的值 stringnullSince 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_type s other than string is null.

如果需要常量值的符号名称,但在常数声明中不允许该值的类型时,或无法在编译时通过 constant_expression 来计算该值,则 readonly 可以改用) 的字段 (Readonly 字段When a symbolic name for a constant value is desired, but when the type of that value is not permitted in a constant declaration, or when the value cannot be computed at compile-time by a constant_expression, a readonly field (Readonly fields) may be used instead.

声明多个常量的常量声明等效于多个具有相同属性、修饰符和类型的单个常量声明。A constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same attributes, modifiers, and type. 例如For example

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

等效于is equivalent to

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

只要依赖项不属于循环本质,就允许常量依赖于同一个程序中的其他常量。Constants are permitted to depend on other constants within the same program as long as the dependencies are not of a circular nature. 编译器会自动排列,以适当的顺序计算常量声明。The compiler automatically arranges to evaluate the constant declarations in the appropriate order. 示例中In the example

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

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

编译器首先计算, A.Y 然后计算 B.Z 并最终计算, A.X 生成值 101112the compiler first evaluates A.Y, then evaluates B.Z, and finally evaluates A.X, producing the values 10, 11, and 12. 常数声明可能依赖于其他程序中的常量,但这种依赖关系只能在一个方向上进行。Constant declarations may depend on constants from other programs, but such dependencies are only possible in one direction. 参考上面的示例,如果 A B 在不同的程序中声明了和,则可能会 A.X 依赖 B.ZB.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 修饰符 (新修饰符) 、 (访问修饰符) 的四个访问修饰符的有效组合 (以及) 的 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 修饰符 (Readonly 字段) 或 volatile 修饰符 (可变字段) 但不能同时包含两者。In addition, a field_declaration may include a readonly modifier (Readonly fields) or a volatile modifier (Volatile fields) but not both. 特性和修饰符适用于 field_declaration 声明的所有成员。The attributes and modifiers apply to all of the members declared by the field_declaration. 同一修饰符在字段声明中多次出现是错误的。It is an error for the same modifier to appear multiple times in a field declaration.

Field_declaration类型 指定声明引入的成员类型。The type of a field_declaration specifies the type of the members introduced by the declaration. 该类型后跟一个 variable_declarator s 列表,其中每个都引入一个新成员。The type is followed by a list of variable_declarator s, 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 字段。 ) 具体而言, readonly 仅允许在以下上下文中对字段进行直接赋值:(A readonly field can be assigned to multiple times in these contexts.) Specifically, direct assignments to a readonly field are permitted only in the following contexts:

  • variable_declarator 中,通过在声明) 中包含 variable_initializer ,引入字段 (。In the variable_declarator that introduces the field (by including a variable_initializer in the declaration).
  • 对于实例字段,在包含字段声明的类的实例构造函数中;对于静态字段,在包含字段声明的类的静态构造函数中。For an instance field, in the instance constructors of the class that contains the field declaration; for a static field, in the static constructor of the class that contains the field declaration. 这也是唯一一种将 readonly 字段作为或参数传递的有效上下文 out refThese 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.

使用常量的静态 readonly 字段Using static readonly fields for constants

static readonly如果需要常数值的符号名称,但在声明中不允许使用值的类型, const 或者在编译时无法计算该值,则字段非常有用。A static readonly field is useful when a symbolic name for a constant value is desired, but when the type of the value is not permitted in a const declaration, or when the value cannot be computed at compile-time. 示例中In the example

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

    private byte red, green, blue;

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

Black、、 White RedGreenBlue 成员不能声明为成员, 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.

常量和静态 readonly 字段的版本控制Versioning of constants and static readonly fields

常量和 readonly 字段的二进制版本控制语义不同。Constants and readonly fields have different binary versioning semantics. 当表达式引用常量时,将在编译时获取常量的值,但当表达式引用 readonly 字段时,不会在运行时获取字段的值。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 即使未重新编译,该语句也会输出新值 Program2Thus, 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 不受影响 Program2However, 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.
  • 类型 bytesbyteshort 、、、、、、、 ushort int uint char float bool System.IntPtrSystem.UIntPtrThe type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr.
  • 具有枚举基类型、、、 byte sbyte short ushortintuint 的 enum_type。An enum_type having an enum base type of byte, sbyte, short, ushort, int, or uint.

示例The example

using System;
using System.Threading;

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

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

    static void Main() {
        finished = false;

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

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

生成输出:produces the output:

result = 143

在此示例中,方法将 Main 启动一个运行方法的新线程 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 从该字段中读取值 result 的情况。If the field finished had not been declared volatile, then it would be permissible for the store to result to be visible to the main thread after the store to finished, and hence for the main thread to read the value 0 from the field result. 声明 finishedvolatile 字段可防止任何此类不一致。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_initializerField declarations may include variable_initializer s. 对于静态字段,变量初始值设定项对应于在类初始化过程中执行的赋值语句。For static fields, variable initializers correspond to assignment statements that are executed during class initialization. 对于实例字段,变量初始值设定项对应于创建类的实例时执行的赋值语句。For instance fields, variable initializers correspond to assignment statements that are executed when an instance of the class is created.

示例The example

using System;

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

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

生成输出produces the output

x = 1.4142135623731, i = 100, s = Hello

由于在执行 x i 实例字段初始值设定项时,将执行静态字段初始值设定项和赋值,并 s 发生赋值。because an assignment to x occurs when static field initializers execute and assignments to i and s occur when the instance field initializers execute.

所有字段都将发生 字段初始化 中所述的默认值初始化,其中包括具有变量初始值设定项的字段。The default value initialization described in Field initialization occurs for all fields, including fields that have variable initializers. 因此,初始化某个类时,该类中的所有静态字段都首先初始化为其默认值,然后以文本顺序执行静态字段初始值设定项。Thus, when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. 同样,在创建类的实例时,该实例中的所有实例字段都将首先初始化为其默认值,然后以文本顺序执行实例字段初始值设定项。Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order.

具有变量初始值设定项的静态字段可以在其默认值状态中观察到。It is possible for static fields with variable initializers to be observed in their default value state. 不过,强烈建议不要使用这种样式。However, this is strongly discouraged as a matter of style. 示例The example

using System;

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

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

展示了这种行为。exhibits this behavior. 尽管 a 和 b 的循环定义,但程序有效。Despite the circular definitions of a and b, the program is valid. 这会导致输出It results in the output

a = 1, b = 2

由于静态字段 a 和初始化为,因此在 b 0 int 执行其初始值设定项之前) (默认值。because the static fields a and b are initialized to 0 (the default value for int) before their initializers are executed. 当的初始值设定项 a 运行时,的值 b 为零,因此 a 将初始化为 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_name 引用任何实例成员时出现编译时错误。Thus, it is a compile-time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple_name. 示例中In the example

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

的变量初始值设定项 y 会导致编译时错误,因为它引用正在创建的实例的成员。the variable initializer for y results in a compile-time error because it references a member of the instance being created.

方法Methods

*方法 _ 是实现可由对象或类执行的计算或操作的成员。A *method _ is a member that implements a computation or action that can be performed by an object or class. 方法使用 _method_declaration * s 进行声明:Methods are declared using _method_declaration*s:

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.
  • 声明最多包含以下修饰符之一: staticvirtualoverrideThe 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 修饰符,则声明不包括以下任何修饰符: staticvirtual sealedexternIf the declaration includes the abstract modifier, then the declaration does not include any of the following modifiers: static, virtual, sealed or extern.
  • 如果声明包含 private 修饰符,则声明不包括以下任何修饰符: virtualoverrideabstractIf 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 修饰符,则不包含以下任何修饰符: newpublicprotected 、、、、、、 internal private virtual sealed override abstractexternIf the declaration includes the partial modifier, then it does not include any of the following modifiers: new, public, protected, internal, private, virtual, sealed, override, abstract, or extern.

具有修饰符的方法 async 是一个异步函数,并遵循 异步函数中所述的规则。A method that has the async modifier is an async function and follows the rules described in Async functions.

方法声明的 return_type 指定由方法计算并返回的值的类型。The return_type of a method declaration specifies the type of the value computed and returned by the method. 如果方法不返回值,则为 return_type voidThe 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_nameinterface_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_list 并且方法不具有修饰符时指定 overrideThe optional type_parameter_constraints_clause s 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.

在方法的 formal_parameter_list 中引用的 return_type 和每个类型必须至少具有与方法本身 (可访问性约束) 相同的可访问性。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*. 语句体包含一个 _block *,它指定在调用方法时要执行的语句。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. 此外,方法的签名必须与在同一类中声明的所有其他方法的签名不同,同一类中声明的两个方法的签名可能不只是与 ref 和不同 outIn addition, the signature of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref and out.

此方法的 type_parameter 在整个 method_declaration 的范围内,可用于在 return_typemethod_bodytype_parameter_constraints_clause 中的整个范围内形成类型,而不是在 属性 中。The method's type_parameter s 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_clause s but not in attributes.

所有形参和类型形参必须具有不同的名称。All formal parameters and type parameters must have different names.

方法参数Method parameters

方法的参数(如果有)由方法的 formal_parameter_list 声明。The parameters of a method, if any, are declared by the method's formal_parameter_list.

formal_parameter_list
    : fixed_parameters
    | fixed_parameters ',' parameter_array
    | parameter_array
    ;

fixed_parameters
    : fixed_parameter (',' fixed_parameter)*
    ;

fixed_parameter
    : attributes? parameter_modifier? type identifier default_argument?
    ;

default_argument
    : '=' expression
    ;

parameter_modifier
    : 'ref'
    | 'out'
    | 'this'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

形参列表包含一个或多个以逗号分隔的参数,其中只有最后一个参数可以是 parameter_arrayThe formal parameter list consists of one or more comma-separated parameters of which only the last may be a parameter_array.

Fixed_parameter 包含一组可选的 特性 (特性) 、可选的 ref outthis 修饰符、类型标识符 和可选 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.

使用 default_argumentfixed_parameter 称为 *可选参数 ,而 没有 default_argument 的 fixed_parameter * 是 *必需的参数A fixed_parameter with a default_argument is known as an optional parameter _, whereas a _fixed_parameter without a default_argument is a *required parameter. 必需的参数不能出现在 _formal_parameter_list * 中的可选参数之后。A required parameter may not appear after an optional parameter in a _formal_parameter_list*.

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:

  • 一个 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_listMi 是必需的 ref 参数, d 是必需的值参数,、 b 和是 s o t 可选值参数, a 是参数数组。In the formal_parameter_list for M, i is a required ref parameter, d is a required value parameter, b, s, o and t are optional value parameters and a is a parameter array.

方法声明为参数、类型参数和局部变量创建了单独的声明空间。A method declaration creates a separate declaration space for parameters, type parameters and local variables. 名称通过方法的类型形参列表和形参列表以及方法 中的局部变量声明引入此声明空间。Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. 方法声明空间的两个成员具有相同的名称是错误的。It is an error for two members of a method declaration space to have the same name. 如果嵌套声明空间的方法声明空间和局部变量声明空间包含具有相同名称的元素,则是错误的。It is an error for the method declaration space and the local variable declaration space of a nested declaration space to contain elements with the same name.

方法调用 (方法 调用) 创建特定于该方法的形参和局部变量的副本,并且调用的参数列表将值或变量引用分配给新创建的形参。A method invocation (Method invocations) creates a copy, specific to that invocation, of the formal parameters and local variables of the method, and the argument list of the invocation assigns values or variable references to the newly created formal parameters. 在方法的 中,可以通过 (简单名称) simple_name 表达式中的标识符引用形参。Within the block of a method, formal parameters can be referenced by their identifiers in simple_name expressions (Simple names).

有四种形参:There are four kinds of formal parameters:

  • 值参数,在没有任何修饰符的情况下声明。Value parameters, which are declared without any modifiers.
  • 引用参数,通过 ref 修饰符声明。Reference parameters, which are declared with the ref modifier.
  • 用修饰符声明的输出参数 outOutput 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.

允许方法为值参数赋值。A method is permitted to assign new values to a value parameter. 此类分配只会影响 value 参数所表示的本地存储位置,它们对方法调用中给定的实参不起作用。Such assignments only affect the local storage location represented by the value parameter—they have no effect on the actual argument given in the method invocation.

引用参数Reference parameters

使用修饰符声明的参数 ref 是一个引用参数。A parameter declared with a ref modifier is a reference parameter. 与值参数不同,引用参数不会创建新的存储位置。Unlike a value parameter, a reference parameter does not create a new storage location. 相反,引用参数表示作为方法调用中的自变量提供的变量所在的存储位置。Instead, a reference parameter represents the same storage location as the variable given as the argument in the method invocation.

如果形参是引用参数,则方法调用中的相应参数必须由关键字 ref 后跟一个 Variable_reference (准确的规则用于确定 与形参相同的类型的明确赋值) 。When a formal parameter is a reference parameter, the corresponding argument in a method invocation must consist of the keyword ref followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. 在将变量作为引用参数传递之前,必须对其进行明确赋值。A variable must be definitely assigned before it can be passed as a reference parameter.

在方法中,引用参数始终被视为明确赋值。Within a method, a reference parameter is always considered definitely assigned.

声明 为迭代器 (迭代 器) 的方法不能有引用参数。A method declared as an iterator (Iterators) cannot have reference parameters.

示例The example

using System;

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

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

生成输出produces the output

i = 2, j = 1

对于 Swap 在中的调用 Mainx 表示 iy 表示 jFor the invocation of Swap in Main, x represents i and y represents j. 因此,调用会对和的值 i 进行交换 jThus, the invocation has the effect of swapping the values of i and j.

在采用引用参数的方法中,多个名称可以表示相同的存储位置。In a method that takes reference parameters it is possible for multiple names to represent the same storage location. 示例中In the example

class A
{
    string s;

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

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

在中,的调用将对 F G 和传递对 s 的引用 a bthe invocation of F in G passes a reference to s for both a and b. 因此,在该调用中,名称 sab 都指同一存储位置,三个分配都修改实例字段 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 是一个输出参数。A parameter declared with an out modifier is an output parameter. 与引用参数类似,output 参数不会创建新的存储位置。Similar to a reference parameter, an output parameter does not create a new storage location. 相反,output 参数表示作为方法调用中的自变量提供的变量所在的存储位置。Instead, an output parameter represents the same storage location as the variable given as the argument in the method invocation.

如果形参为 output 参数,则方法调用中的相应参数必须由关键字 out 后跟一个 Variable_reference (准确的规则用于确定 与形参相同的类型的明确赋值) 。When a formal parameter is an output parameter, the corresponding argument in a method invocation must consist of the keyword out followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. 在将变量作为 output 参数传递之前,不需要明确赋值,但在将变量作为 output 参数传递的调用之后,该变量被视为明确赋值。A variable need not be definitely assigned before it can be passed as an output parameter, but following an invocation where a variable was passed as an output parameter, the variable is considered definitely assigned.

在方法中,就像局部变量一样,output 参数最初被视为未分配,在使用其值之前必须进行明确赋值。Within a method, just like a local variable, an output parameter is initially considered unassigned and must be definitely assigned before its value is used.

方法返回之前,必须为方法的每个输出参数进行明确赋值。Every output parameter of a method must be definitely assigned before the method returns.

声明为分部方法 (分部方法) 或迭代器 (迭代 器的方法) 不能有输出参数。A method declared as a partial method (Partial methods) or an iterator (Iterators) cannot have output parameters.

输出参数通常用于生成多个返回值的方法。Output parameters are typically used in methods that produce multiple return values. 例如:For example:

using System;

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

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

该示例生成以下输出:The example produces the output:

c:\Windows\System\
hello.txt

请注意, 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 修饰符与修饰符和组合在一起 ref outIt 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[] ,该方法的正常形式与单个参数的所花费形式之间可能存在歧义 objectWhen 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

在的第一次和最后一次调用中 FF 是适用的,因为存在从参数类型到参数类型的隐式转换 (都是 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 (其中, R C 或派生自 C) 的类,则按如下方式处理调用:In precise terms, when a method named N is invoked with an argument list A on an instance with a compile-time type C and a run-time type R (where R is either C or a class derived from C), the invocation is processed as follows:

  • 首先,将重载决策应用于 CNA ,以便从中声明的方法集中选择特定的方法, 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 为虚方法,并调用与有关的派生程度最高的 M ROtherwise, 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 RThe 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 在与相同的程序中声明 C 的方法)。For the purposes of locating the overridden base method, a method is considered accessible if it is public, if it is protected, if it is protected internal, or if it is internal and declared in the same program as C.

除非以下所有条件都适用于重写声明,否则将发生编译时错误:A compile-time error occurs unless all of the following are true for an override declaration:

  • 重写的基方法可以按上文所述进行定位。An overridden base method can be located as described above.
  • 正好有一个这种重写基方法。There is exactly one such overridden base method. 仅当基类类型为构造类型时,此限制才会生效,在这种情况下,类型参数的替换会使两个方法的签名相同。This restriction has effect only if the base class type is a constructed type where the substitution of type arguments makes the signature of two methods the same.
  • 重写的基方法为虚拟、抽象或重写方法。The overridden base method is a virtual, abstract, or override method. 换言之,重写的基方法不能是静态的或非虚的。In other words, the overridden base method cannot be static or non-virtual.
  • 重写的基方法不是密封的方法。The overridden base method is not a sealed method.
  • 重写方法和重写的基方法具有相同的返回类型。The override method and the overridden base method have the same return type.
  • 重写声明和重写的基方法具有相同的声明的可访问性。The override declaration and the overridden base method have the same declared accessibility. 换言之,重写声明不能更改虚拟方法的可访问性。In other words, an override declaration cannot change the accessibility of the virtual method. 但是,如果重写的基方法是受保护的内部方法,并且在与包含 override 方法的程序集不同的程序集中声明,则必须保护 override 方法的声明的可访问性。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. 因此,中的声明 F C 允许重写 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 ,或者该方法为 async 并且返回类型为,则为方法的结果类型 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 不允许在块中 (返回语句) 的语句指定表达式。When a method has a void result type and a block body, return statements (The return statement) in the block are not permitted to specify an expression. 如果 void 方法的执行块正常完成 (即,控制流从方法体的末尾) ,则该方法只返回到当前调用方。If execution of the block of a void method completes normally (that is, control flows off the end of the method body), that method simply returns to its current caller.

如果方法具有 void 结果和表达式体,则表达式 E 必须为 statement_expression,且正文完全等效于窗体的块体 { E; }When a method has a void result and an expression body, the expression E must be a statement_expression, and the body is exactly equivalent to a block body of the form { E; }.

当方法具有非 void 结果类型和块体时, return 块中的每个语句都必须指定一个可隐式转换为结果类型的表达式。When a method has a non-void result type and a block body, each return statement in the block must specify an expression that is implicitly convertible to the result type. 返回值的方法的块体的终结点必须是无法访问的。The endpoint of a block body of a value-returning method must not be reachable. 换言之,在带有块体的值返回方法中,不允许控件流过方法体的末尾。In other words, in a value-returning method with a block body, control is not permitted to flow off the end of the method body.

如果方法具有非 void 结果类型和表达式体,则表达式必须可隐式转换为结果类型,并且正文与窗体的块体完全等效 { return E; }When a method has a non-void result type and an expression body, the expression must be implicitly convertible to the result type, and the body is exactly equivalent to a block body of the form { return E; }.

示例中In the example

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

    public int G() {
        return 1;
    }

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

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

返回值 F 方法会导致编译时错误,因为控件可以流过方法体的末尾。the value-returning F method results in a compile-time error because control can flow off the end of the method body. 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_declaration s 来声明:Properties are declared using property_declaration s:

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_nameinterface_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_declaration 和/或 set_accessor_declaration 组成。The accessor declarations consist of a get_accessor_declaration, a set_accessor_declaration, or both. 每个访问器声明都包含标记, get set 后跟可选的 accessor_modifieraccessor_bodyEach accessor declaration consists of the token get or set followed by an optional accessor_modifier and an accessor_body.

Accessor_modifier 的使用受以下限制的制约:The use of accessor_modifier s 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_modifierget set 只允许在其中一种访问器上使用。For a property or indexer that has no override modifier, an accessor_modifier is permitted only if the property or indexer has both a get and set accessor, and then is permitted only on one of those accessors.
  • 对于包含修饰符的属性或索引器 override ,访问器必须与要重写的访问器的 accessor_modifier(如果有)匹配。For a property or indexer that includes an override modifier, an accessor must match the accessor_modifier, if any, of the accessor being overridden.
  • Accessor_modifier 必须声明一个可访问性,严格比属性或索引器本身的声明的可访问性更严格。The accessor_modifier must declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. 精确:To be precise:
    • 如果属性或索引器具有的声明的可访问性 public ,则 accessor_modifier 可以是 protected internal 、、 internal protectedprivateIf the property or indexer has a declared accessibility of public, the accessor_modifier may be either protected internal, internal, protected, or private.
    • 如果属性或索引器具有的声明的可访问性 protected internal ,则 accessor_modifier 可以 internal 是、 protectedprivateIf 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_modifierIf 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. 非抽象的非 extern 属性可能具有每个 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. 对于任何其他非抽象非 extern 属性的访问器,_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 ,因此访问器中的局部变量或常量声明会出现编译时错误 setSince 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 返回存储在私有字段中的字符串 captionThe 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 上面的类,以下是属性使用的示例 CaptionGiven the Button class above, the following is an example of use of the Caption property:

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

此处, set 通过将值分配给属性来调用访问器,并 get 通过引用表达式中的属性调用访问器。Here, the set accessor is invoked by assigning a value to the property, and the get accessor is invoked by referencing the property in an expression.

get属性的和 set 访问器不是不同的成员,并且不能单独声明属性的访问器。The get and set accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. 因此,读写属性的两个访问器不可能具有不同的可访问性。As such, it is not possible for the two accessors of a read-write property to have different accessibility. 示例The example

class A
{
    private string name;

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

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

不声明单个读写属性。does not declare a single read-write property. 相反,它声明了两个具有相同名称的属性,一个为只读,一个只写。Rather, it declares two properties with the same name, one read-only and one write-only. 由于在同一类中声明的两个成员不能具有相同的名称,因此该示例将导致发生编译时错误。Since two members declared in the same class cannot have the same name, the example causes a compile-time error to occur.

当派生类用与继承属性相同的名称声明属性时,派生属性将隐藏与读取和写入相关的继承属性。When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing. 示例中In the example

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

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

中的 P 属性在 B 中隐藏属性, P A 与读取和写入有关。the P property in B hides the P property in A with respect to both reading and writing. 因此,在语句中Thus, in the statements

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

的赋值 b.P 导致报告编译时错误,因为中的只读 P 属性 B 隐藏了中的只写 P 属性 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. 该位置作为 XY 属性以及 Location 类型的属性公开公开 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 字段,则无法对类进行此类更改 LabelHad 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类包含三个属性: InOutError ,分别表示标准输入、输出和错误设备。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

对于 short) ,自动实现的属性 (或 自动属性 ,是具有仅包含分号的访问器正文的非抽象非 extern 属性。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. 如果 auto 属性没有 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. 此类赋值直接分配给属性的 readonly 支持字段。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.
  • 如果使用情况是复合 赋值) (复合赋值的 目标,或者作为 ++ or 运算符的目标 -- (函数成员.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.CountIn 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 string Prop {
        get { return "April"; }       // Must not have a modifier here
        internal set {...}            // Ok, because I.Prop has no set accessor
    }
}

虚拟、密封、重写和抽象属性访问器Virtual, sealed, override, and abstract property accessors

virtual属性声明指定属性的访问器是虚拟的。A virtual property declaration specifies that the accessors of the property are virtual. virtual修饰符适用于读写属性的这两个访问器,即读写属性的一个取值函数是虚拟的。The virtual modifier applies to both accessors of a read-write property—it is not possible for only one accessor of a read-write property to be virtual.

abstract属性声明指定属性的访问器是虚拟的,但是不提供访问器的实际实现。An abstract property declaration specifies that the accessors of the property are virtual, but does not provide an actual implementation of the accessors. 相反,非抽象派生类需要通过重写属性来为访问器提供自己的实现。Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the property. 因为抽象属性声明的访问器不提供实际的实现,所以其 accessor_body 只包含一个分号。Because an accessor for an abstract property declaration provides no actual implementation, its accessor_body simply consists of a semicolon.

同时包含和修饰符的属性声明 abstract override 指定属性是抽象的并且会重写基属性。A property declaration that includes both the abstract and override modifiers specifies that the property is abstract and overrides a base property. 此类属性的访问器也是抽象的。The accessors of such a property are also abstract.

只有抽象类 (抽象 ) 才能使用抽象属性声明。在派生类中,可以通过包含指定指令的属性声明,在派生类中重写继承的虚拟属性的访问器 overrideAbstract 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.

除了声明和调用语法中的差异外,virtual、sealed、override 和 abstract 访问器的行为与虚拟、密封、重写和抽象方法完全相同。Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. 具体而言,在 虚拟方法替代方法密封方法抽象方法 中描述的规则将应用为:访问器是相应形式的方法:Specifically, the rules described in Virtual methods, Override methods, Sealed methods, and Abstract methods apply as if accessors were methods of a corresponding form:

  • get访问器对应于带有属性类型返回值的无参数方法和包含属性的修饰符。A get accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property.
  • set访问器对应于一个方法,该方法具有属性类型的单个值参数、 void 返回类型以及与包含属性相同的修饰符。A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.

示例中In the example

abstract class A
{
    int y;

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

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

    public abstract int Z { get; set; }
}

X 是虚拟的只读属性, Y 是虚拟读写属性, Z 是抽象读写属性。X is a virtual read-only property, Y is a virtual read-write property, and Z is an abstract read-write property. 由于 Z 是抽象的,因此 A 还必须将包含类声明为抽象类。Because Z is abstract, the containing class A must also be declared abstract.

派生自的类 A 如下所示:A class that derives from A is show below:

class B: A
{
    int z;

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

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

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

此处,、和的 X 声明 Y Z 会重写属性声明。Here, the declarations of X, Y, and Z are overriding property declarations. 每个属性声明都与相应的继承属性的可访问性修饰符、类型和名称完全匹配。Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. get 访问器 X 和的访问 setY 使用 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_declaration s 声明事件:Events are declared using event_declaration s:

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 的非抽象事件,编译器会自动向其提供 (类似于字段的事件) ;对于 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_declarator 一个事件。An event declaration that omits event_accessor_declarations defines one or more events—one for each of the variable_declarator s. 特性和修饰符适用于由此类 event_declaration 声明的所有成员。The attributes and modifiers apply to all of the members declared by such an event_declaration.

Event_declaration 同时包含 abstract 修饰符和大括号分隔的 event_accessor_declarations 会导致编译时错误。It is a compile-time error for an event_declaration to include both the abstract modifier and brace-delimited event_accessor_declarations.

当事件声明包含修饰符时 extern ,该事件被称为 *外部事件 _。When an event declaration includes an extern modifier, the event is said to be an *external event _. 由于外部事件声明不提供实际的实现,因此它在同时包含 extern 修饰符和 _event_accessor_declarations * 时是错误的。Because an external event declaration provides no actual implementation, it is an error for it to include both the extern modifier and _event_accessor_declarations*.

使用或修饰符的事件声明的 variable_declarator abstract external 包含 variable_initializer 时,会发生编译时错误。It is a compile-time error for a variable_declarator of an event declaration with an abstract or external modifier to include a variable_initializer.

事件可用作和运算符的左操作数 +=-= (事件分配) 。An event can be used as the left-hand operand of the += and -= operators (Event assignment). 这些运算符分别用于将事件处理程序附加到事件处理程序或从事件中删除事件处理程序,事件的访问修饰符控制允许此类操作的上下文。These operators are used, respectively, to attach event handlers to or to remove event handlers from an event, and the access modifiers of the event control the contexts in which such operations are permitted.

由于 +=-= 是对声明事件的类型之外的事件唯一允许的操作,外部代码可以添加和移除事件的处理程序,但不能以任何其他方式获取或修改事件处理程序的基础列表。Since += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

在窗体的操作中 x += y x -= y ,或在 x 为事件并且引用出现在包含的声明的类型之外时,操作的 x 结果具有类型 void (而不是类型,其值为 x x 后) 赋值。In an operation of the form x += y or x -= y, when x is an event and the reference takes place outside the type that contains the declaration of x, the result of the operation has type void (as opposed to having the type of x, with the value of x after the assignment). 此规则禁止外部代码间接检查事件的基础委托。This rule prohibits external code from indirectly examining the underlying delegate of an event.

下面的示例演示如何将事件处理程序附加到类的实例 ButtonThe 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 用作类中的字段 ButtonClick 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.

在类的声明外部 ButtonClick 成员只能用于 += 和运算符的左侧 -= ,如下所示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(...);

这会从事件的调用列表中移除委托 Clickwhich 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. 添加和移除操作是线程安全的,并且可能 (但不需要) 在将锁语句锁定 (锁语句) 在实例事件的包含对象上,或类型对象 (静态事件) 匿名对象创建表达式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 ButtonEvent 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_declarationremove_accessor_declaration 组成。The accessor declarations consist of an add_accessor_declaration and a remove_accessor_declaration. 每个访问器声明都包含标记 addremove 后跟 Each accessor declaration consists of the token add or remove followed by a block. add_accessor_declaration 关联的 指定在添加事件处理程序时要执行的语句,与 remove_accessor_declaration 关联的 指定在删除事件处理程序时要执行的语句。The block associated with an add_accessor_declaration specifies the statements to execute when an event handler is added, and the block associated with a remove_accessor_declaration specifies the statements to execute when an event handler is removed.

每个 add_accessor_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.

同时包含和修饰符的事件声明 abstract override 指定事件是抽象的,并且会重写基事件。An event declaration that includes both the abstract and override modifiers specifies that the event is abstract and overrides a base event. 此类事件的访问器也是抽象的。The accessors of such an event are also abstract.

仅在抽象类 (抽象 ) 中允许抽象事件声明。Abstract event declarations are only permitted in abstract classes (Abstract classes).

通过包含指定修饰符的事件声明,可在派生类中重写继承的虚拟事件的访问器 overrideThe 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.

重写事件声明包含修饰符是编译时错误 newIt is a compile-time error for an overriding event declaration to include a new modifier.

除了声明和调用语法中的差异外,virtual、sealed、override 和 abstract 访问器的行为与虚拟、密封、重写和抽象方法完全相同。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_declaration * s 声明索引器:Indexers are declared using _indexer_declaration*s:

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.

索引器声明服从与方法声明相同的规则, (方法 与) 的修饰符的有效组合相同,但有一个例外,即索引器声明中不允许使用 static 修饰符。Indexer declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers, with the one exception being that the static modifier is not permitted on an indexer declaration.

virtual override abstract 除一种情况外,修饰符、和都是互斥的。The modifiers virtual, override, and abstract are mutually exclusive except in one case. 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. 索引器的形参列表与 (方法参数) 方法的形参列表相同,不同之处在于至少必须指定一个形参,并且 ref out 不允许和参数修饰符。The formal parameter list of an indexer corresponds to that of a method (Method parameters), except that at least one parameter must be specified, and that the ref and out parameter modifiers are not permitted.

索引器的 类型formal_parameter_list 中引用的每个类型必须至少与索引器本身 (可访问 性约束) 相同。The type of an indexer and each of the types referenced in the formal_parameter_list must be at least as accessible as the indexer itself (Accessibility constraints).

Indexer_body 可以是由 访问器体 _ 或 _表达式体*_ 组成的。An indexer_body may either consist of an accessor body _ or an _expression body*_. 在访问器主体中,_accessor_declarations * (必须括在 " { " 和 " } " 标记中)声明属性的访问器 (访问 器) 。In an accessor body, _accessor_declarations*, which must be enclosed in "{" and "}" tokens, declare the accessors (Accessors) of the property. 访问器指定与读取和写入属性相关联的可执行语句。The accessors specify the executable statements associated with reading and writing the property.

=>由 "" 后跟表达式和分号的表达式体 E 完全等效于语句体 { get { return E; } } ,因此只能用于指定仅限 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和 a 之间的 primes: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

*Operator _ 是一个成员,用于定义可应用于类的实例的表达式运算符的含义。An *operator _ is a member that defines the meaning of an expression operator that can be applied to instances of the class. 运算符使用 _operator_declaration * s 进行声明:Operators are declared using _operator_declaration*s:

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

有三种类型的可重载运算符:一元运算符 (一元运算符) ,二元运算符 (二元运算符) ,And 转换运算符 (转换运算符) 。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*. 语句体包含一个 _block *,它指定在调用运算符时要执行的语句。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:

  • 运算符声明必须同时包含 publicstatic 修饰符。An operator declaration must include both a public and a static modifier.
  • 运算符的参数 (s) 必须是值参数 (值参数) 。The parameter(s) of an operator must be value parameters (Value parameters). 运算符声明要指定或参数,这是编译时错误 ref outIt is a compile-time error for an operator declaration to specify ref or out parameters.
  • 运算符 (一元运算符二元运算符转换运算符) 的签名必须与同一类中声明的所有其他运算符的签名不同。The signature of an operator (Unary operators, Binary operators, Conversion operators) must differ from the signatures of all other operators declared in the same class.
  • 运算符声明中引用的所有类型必须至少具有与运算符本身相同的可访问 性 (辅助功能约束) 。All types referenced in an operator declaration must be at least as accessible as the operator itself (Accessibility constraints).
  • 同一修饰符在运算符声明中多次出现是错误的。It is an error for the same modifier to appear multiple times in an operator declaration.

每个运算符类别都有其他限制,如以下各节所述。Each operator category imposes additional restrictions, as described in the following sections.

与其他成员一样,基类中声明的运算符由派生类继承。Like other members, operators declared in a base class are inherited by derived classes. 由于运算符声明始终需要声明运算符的类或结构参与运算符的签名,因此,派生类中声明的运算符不能隐藏在基类中声明的运算符。Because operator declarations always require the class or struct in which the operator is declared to participate in the signature of the operator, it is not possible for an operator declared in a derived class to hide an operator declared in a base class. 因此, new 在运算符声明中从不需要修饰符,因此永远不允许使用。Thus, the new modifier is never required, and therefore never permitted, in an operator declaration.

有关一元运算符和二元运算符的其他信息,请参阅 运算符Additional information on unary and binary operators can be found in Operators.

有关转换运算符的其他信息,请参阅 用户定义的转换Additional information on conversion operators can be found in User-defined conversions.

一元运算符Unary operators

以下规则适用于一元运算符声明,其中 T 表示包含运算符声明的类或结构的实例类型:The following rules apply to unary operator declarations, where T denotes the instance type of the class or struct that contains the operator declaration:

  • 一元 +-!~ 运算符必须采用或类型的单个参数 T T? ,并且可以返回任何类型。A unary +, -, !, or ~ operator must take a single parameter of type T or T? and can return any type.
  • 一元 ++-- 运算符必须采用或类型的单个参数 T T? ,并且必须返回该相同类型或从其派生的类型。A unary ++ or -- operator must take a single parameter of type T or T? and must return that same type or a type derived from it.
  • 一元 truefalse 运算符必须采用或类型的单个参数 T T? ,并且必须返回类型 boolA unary true or false operator must take a single parameter of type T or T? and must return type bool.

一元运算符的签名由运算符标记 (、、、、、、 + - ! ~ ++ -- truefalse) 以及单个形参的类型组成。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. true false 用户定义的条件逻辑运算符布尔表达式中进一步介绍了和运算符。The true and false operators are described further in User-defined conditional logical operators and Boolean expressions.

下面的示例演示了一个整数向量类的实现和后续使用 operator ++The following example shows an implementation and subsequent usage of operator ++ for an integer vector class:

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

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

    public int this[int index] {...}        // read-write indexer

    public static IntVector operator ++(IntVector iv) {
        IntVector temp = new IntVector(iv.Length);
        for (int i = 0; i < iv.Length; i++)
            temp[i] = iv[i] + 1;
        return temp;
    }
}

class Test
{
    static void Main() {
        IntVector iv1 = new IntVector(4);    // vector of 4 x 0
        IntVector iv2;

        iv2 = iv1++;    // iv2 contains 4 x 0, iv1 contains 4 x 1
        iv2 = ++iv1;    // iv2 contains 4 x 2, iv1 contains 4 x 2
    }
}

请注意,operator 方法如何返回通过将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.
  • 二元 <<>> 运算符必须采用两个参数,其中第一个参数的类型必须为,第二个参数必须 T T? 具有类型 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 S T 分别为和。For a given source type S and target type T, if S or T are nullable types, let S0 and T0 refer to their underlying types, otherwise S0 and T0 are equal to S and T respectively. S T 仅当满足以下所有条件时,才允许类或结构声明从源类型到目标类型的转换:A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true:

  • 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.
  • S0T0 都不是 interface_typeNeither S0 nor T0 is an interface_type.
  • 如果不包括用户定义的转换,则从到的转换不存在 S T T SExcluding user-defined conversions, a conversion does not exist from S to T or from T to S.

对于这些规则,与或关联的任何类型参数 S 都被 T 视为唯一的类型,这些类型与其他类型没有继承关系,并忽略这些类型参数上的任何约束。For the purposes of these rules, any type parameters associated with S or T are considered to be unique types that have no inheritance relationship with other types, and any constraints on those type parameters are ignored.

示例中In the example

class C<T> {...}

class D<T>: C<T>
{
    public static implicit operator C<int>(D<T> value) {...}      // Ok
    public static implicit operator C<string>(D<T> value) {...}   // Ok
    public static implicit operator C<T>(D<T> value) {...}        // Error
}

允许使用前两个运算符声明,因为对于 索引器.3,和 T 分别被 int string 视为唯一的类型而没有关系。the first two operator declarations are permitted because, for the purposes of Indexers.3, T and int and string respectively are considered unique types with no relationship. 但是,第三个运算符是错误,因为 C<T> 是的基类 D<T>However, the third operator is an error because C<T> is the base class of D<T>.

从第二个规则开始,转换运算符必须在声明运算符的类或结构类型中转换为或。From the second rule it follows that a conversion operator must convert either to or from the class or struct type in which the operator is declared. 例如,类或结构类型可以 C 定义从 Cint 和到之间的转换 int C ,但不 int bool 能定义从到的转换。For example, it is possible for a class or struct type C to define a conversion from C to int and from int to C, but not from int to bool.

不能直接重新定义预定义的转换。It is not possible to directly redefine a pre-defined conversion. 因此,不允许转换运算符从或转换为, object 因为 object 和所有其他类型之间已存在隐式和显式转换。Thus, conversion operators are not allowed to convert from or to object because implicit and explicit conversions already exist between object and all other types. 同样,转换的源类型和目标类型都不能是另一类型的基类型,因为转换已经存在。Likewise, neither the source nor the target types of a conversion can be a base type of the other, since a conversion would then already exist.

但是,可以在针对特定类型参数的泛型类型上声明运算符,指定已作为预定义转换存在的转换。However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions. 示例中In the example

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

当 type object 指定为的类型参数时 T ,第二个运算符将声明已存在 (隐式的转换,因此,从任何类型到类型) 的显式转换也是如此 objectwhen 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 TS 则将忽略从到的隐式或显式) (的所有用户定义的转换 TIf 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 ,则将忽略从到的任何用户定义的显式转换 S TIf a pre-defined explicit conversion (Explicit conversions) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. 此外:Furthermore:

如果 T 是接口类型,则将忽略从到的用户定义的隐式转换 S TIf T is an interface type, user-defined implicit conversions from S to T are ignored.

否则, S 仍会考虑从到的用户定义的隐式转换 TOtherwise, 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_typeUser-defined conversions are not allowed to convert from or to interface_type s. 特别是,此限制可确保在转换为 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. (请注意,这是返回类型参与签名的唯一成员窗体。 ) implicit explicit 转换运算符的或分类不是运算符签名的一部分。(Note that this is the only form of member for which the return type participates in the signature.) The implicit or explicit classification of a conversion operator is not part of the operator's signature. 因此,类或结构不能同时声明 implicit explicit 具有相同源和目标类型的和转换运算符。Thus, a class or struct cannot declare both an implicit and an explicit conversion operator with the same source and target types.

通常,用户定义的隐式转换应设计为从不引发异常,并且永远不会丢失信息。In general, user-defined implicit conversions should be designed to never throw exceptions and never lose information. 例如,如果用户定义的转换可以给出异常 (例如,因为 source 参数超出范围) 或丢失信息 (如丢弃高序位) ,则应将该转换定义为显式转换。If a user-defined conversion can give rise to exceptions (for example, because the source argument is out of range) or loss of information (such as discarding high-order bits), then that conversion should be defined as an explicit conversion.

示例中In the example

using System;

public struct Digit
{
    byte value;

    public Digit(byte value) {
        if (value < 0 || value > 9) throw new ArgumentException();
        this.value = value;
    }

    public static implicit operator byte(Digit d) {
        return d.value;
    }

    public static explicit operator Digit(byte b) {
        return new Digit(b);
    }
}

从到的 Digit 转换 byte 是隐式的,因为它从不引发异常或丢失信息,而从到的转换 byte 是显式的,因为只能表示的 Digit Digit 可能值的子集 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_declaration * s 进行声明:Instance constructors are declared using _constructor_declaration*s:

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_expression s (对象创建表达式) 和 constructor_initializer 来调用的。Instance constructors are invoked by object_creation_expression s (Object creation expressions) and through constructor_initializer s.

构造函数初始值设定项Constructor initializers

除类) 的实例构造函数外,所有实例构造函数都 object 隐式包含在 constructor_body 之前的另一个实例构造函数的调用 (。All instance constructors (except those for class object) implicitly include an invocation of another instance constructor immediately before the constructor_body. 隐式调用的构造函数由 constructor_initializer 确定:The constructor to implicitly invoke is determined by the constructor_initializer:

  • 窗体的实例构造函数初始值设定项, base(argument_list)base() 导致调用直接基类的实例构造函数。An instance constructor initializer of the form base(argument_list) or base() causes an instance constructor from the direct base class to be invoked. 使用 argument_list 如果存在,则选择该构造函数,并 使用重载决策的重载决策规则。That constructor is selected using argument_list if present and the overload resolution rules of Overload resolution. 候选实例构造函数集包括直接基类中包含的所有可访问实例构造函数,或者默认构造函数 (默认 构造函数) 如果未在直接基类中声明任何实例构造函数,则为默认构造函数。The set of candidate instance constructors consists of all accessible instance constructors contained in the direct base class, or the default constructor (Default constructors), if no instance constructors are declared in the direct base class. 如果此集为空,或者无法识别单个最佳实例构造函数,则会发生编译时错误。If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs.
  • 窗体的实例构造函数初始值设定项, this(argument-list)this() 导致调用类自身的实例构造函数。An instance constructor initializer of the form this(argument-list) or this() causes an instance constructor from the class itself to be invoked. 如果存在,则使用 argument_list ,并使用 重载决策的重载决策规则来选择构造函数。The constructor is selected using argument_list if present and the overload resolution rules of Overload resolution. 候选实例构造函数集包含类本身中声明的所有可访问实例构造函数。The set of candidate instance constructors consists of all accessible instance constructors declared in the class itself. 如果此集为空,或者无法识别单个最佳实例构造函数,则会发生编译时错误。If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs. 如果实例构造函数声明包含调用构造函数本身的构造函数初始值设定项,则会发生编译时错误。If an instance constructor declaration includes a constructor initializer that invokes the constructor itself, a compile-time error occurs.

如果实例构造函数没有构造函数初始值设定项, base() 则会隐式提供窗体的构造函数初始值设定项。If an instance constructor has no constructor initializer, a constructor initializer of the form base() is implicitly provided. 因此,形式的实例构造函数声明Thus, an instance constructor declaration of the form

C(...) {...}

完全等效于is exactly equivalent to

C(...): base() {...}

实例构造函数声明的 formal_parameter_list 给定的参数的范围包含该声明的构造函数初始值设定项。The scope of the parameters given by the formal_parameter_list of an instance constructor declaration includes the constructor initializer of that declaration. 因此,可以使用构造函数初始值设定项来访问构造函数的参数。Thus, a constructor initializer is permitted to access the parameters of the constructor. 例如:For example:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y): base(x + y, x - y) {}
}

实例构造函数初始值设定项无法访问正在创建的实例。An instance constructor initializer cannot access the instance being created. 因此 this ,在构造函数初始值设定项的参数表达式中引用编译时错误,这是因为参数表达式的编译时错误通过 simple_name 引用任何实例成员。Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple_name.

实例变量初始值设定项Instance variable initializers

如果实例构造函数不具有构造函数初始值设定项,或者它具有形式的构造函数初始值设定项 base(...) ,则该构造函数将隐式执行由其类中声明的实例字段的 variable_initializer 指定的初始化。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_initializer s 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_body 前面的语句是非常有用的。It is useful to think of instance variable initializers and constructor initializers as statements that are automatically inserted before the constructor_body. 示例The example

using System;
using System.Collections;

class A
{
    int x = 1, y = -1, count;

    public A() {
        count = 0;
    }

    public A(int n) {
        count = n;
    }
}

class B: A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100) {
        items.Add("default");
    }

    public B(int n): base(n - 1) {
        max = n;
    }
}

包含若干变量初始值设定项;它还包含两个窗体的构造函数初始值设定项 (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_declaration * s 声明静态构造函数:Static constructors are declared using _static_constructor_declaration*s:

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_bodyvoid (方法体) 的返回类型。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 方法 (应用程序启动) 在其中开始执行,则在调用方法之前,将执行该类的静态构造函数 MainIf 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.YB 的静态构造函数之前运行的初始化表达式。To execute the Main method, the system first runs the initializer for B.Y, prior to class B's static constructor. Y的初始值设定项会导致 A 运行静态构造函数,因为引用的值 A.XY'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();
   }
}

is

B's destructor
A's destructor

由于继承链中的析构函数按顺序调用,从最常派生到最小派生。since destructors in an inheritance chain are called in order, from most derived to least derived.

通过在上重写虚拟方法来实现析构函数 Finalize System.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.

迭代器Iterators

函数成员 (函数 成员) 使用迭代器块实现 () 称为 迭代器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>. 为了简单起见,在本章中,这些接口分别作为和引用 IEnumerator IEnumerator<T>For the sake of brevity, in this chapter these interfaces are referenced as IEnumerator and IEnumerator<T>, respectively.

可枚举接口Enumerable interfaces

枚举接口 是非泛型接口 System.Collections.IEnumerable 和泛型接口的所有实例化 System.Collections.Generic.IEnumerable<T>The enumerable interfaces are the non-generic interface System.Collections.IEnumerable and all instantiations of the generic interface System.Collections.Generic.IEnumerable<T>. 为了简单起见,在本章中,这些接口分别作为和引用 IEnumerable IEnumerable<T>For the sake of brevity, in this chapter these interfaces are referenced as IEnumerable and IEnumerable<T>, respectively.

Yield 类型Yield type

迭代器生成一个值序列,该序列具有相同的类型。An iterator produces a sequence of values, all of the same type. 此类型称为迭代器的 yield 类型This type is called the yield type of the iterator.

  • 返回或的迭代器的 yield 类型 IEnumerator IEnumerableobjectThe 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. 此对象封装在迭代器块中指定的代码,并在调用枚举器对象的方法时,在迭代器块中执行代码 MoveNextThis 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.

以下各节描述了 MoveNext Current Dispose IEnumerable IEnumerable<T> 枚举器对象提供的和接口实现的、和成员的确切行为。The following sections describe the exact behavior of the MoveNext, Current, and Dispose members of the IEnumerable and IEnumerable<T> interface implementations provided by an enumerator object.

请注意,枚举器对象不支持 IEnumerator.Reset 方法。Note that enumerator objects do not support the IEnumerator.Reset method. 调用此方法将导致 System.NotSupportedException 引发。Invoking this method causes a System.NotSupportedException to be thrown.

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 取决于调用时枚举器对象的状态 MoveNextThe precise action performed by MoveNext depends on the state of the enumerator object when MoveNext is invoked:

  • 如果枚举器对象的状态在 之前,则调用 MoveNextIf 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.
  • 如果枚举器对象的状态为 "已 挂起",则调用 MoveNextIf 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).
  • 如果枚举数对象的状态为 after,则调用 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 returnThe 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.
    • 枚举器对象的状态将更改为 afterThe 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:
    • 枚举器对象的状态将更改为 afterThe 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.
    • 枚举器对象的状态将更改为 afterThe state of the enumerator object is changed to after.
    • 异常传播继续到方法的调用方 MoveNextThe exception propagation continues to the caller of the MoveNext method.

当前属性The Current property

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

如果枚举器对象处于 *挂起 _ 状态,则的值 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 类型不是的迭代器 objectCurrent 通过枚举器对象的实现访问的结果 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方法用于通过将枚举器对象的状态设置为 after 来清理迭代。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.
  • 如果枚举器对象的状态为 "已 挂起",则调用 DisposeIf the state of the enumerator object is suspended, invoking Dispose:
    • 将状态更改为 正在运行Changes the state to running.
    • 执行所有 finally 块,就好像最后执行的 yield return 语句是 yield break 语句一样。Executes any finally blocks as if the last executed yield return statement were a yield break statement. 如果这导致引发异常并将其传播到迭代器主体外,则枚举器对象的状态将设置为 after ,并将异常传播到方法的调用方 DisposeIf 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.
    • 将状态更改为 afterChanges the state to after.
  • 如果枚举数对象的状态为 after,则调用 Dispose 不会有任何影响。If the state of the enumerator object is after, invoking Dispose has no affect.

可枚举对象Enumerable objects

当使用迭代器块实现返回可枚举接口类型的函数成员时,调用函数成员不会立即执行迭代器块中的代码。When a function member returning an enumerable interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. 相反,将创建并返回一个可 枚举对象Instead, an enumerable object is created and returned. 可枚举对象的 GetEnumerator 方法返回一个枚举器对象,该对象封装迭代器块中指定的代码,并在调用枚举器对象的方法时,在迭代器块中执行代码 MoveNextThe 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 和接口的方法的实现 IEnumerable IEnumerable<T>An enumerable object provides an implementation of the GetEnumerator methods of the IEnumerable and IEnumerable<T> interfaces. 这两个 GetEnumerator 方法共享一个公共实现,该实现获取并返回可用的枚举器对象。The two GetEnumerator methods share a common implementation that acquires and returns an available enumerator object. 初始化枚举器对象时,将在初始化可枚举对象时保存的参数值和实例值进行初始化,否则枚举器对象的功能如 枚举器对象中所述。The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in Enumerator objects.

实现示例Implementation example

本部分介绍在标准 c # 构造中可能的迭代器实现。This section describes a possible implementation of iterators in terms of standard C# constructs. 此处所述的实现基于 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();
        }
    }
}

可枚举的类既实现可枚举接口又实现枚举器接口,使其既可用作可枚举的,也可用作枚举器。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 如果在为时调用,则该方法将引发 __state 0The 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.

下面的示例演示一个简单的 tree 类。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. 请注意,不能用简单的语句编写翻译后的代码 foreachNote that it is not possible to write the translated code with simple foreach statements.

异步函数Async functions

方法 (方法) 或匿名函数 (匿名函数表达式) 使用 async 修饰符称为 *async 函数 _。A method (Methods) or anonymous function (Anonymous function expressions) with the async modifier is called an *async function _. 通常,术语 _ async* 用于描述具有修饰符的任何类型的函数 asyncIn 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>. 为了简单起见,在本章中,这些类型分别作为和引用 Task Task<T>For the sake of brevity, in this chapter these types are referenced as Task and Task<T>, respectively. 返回任务类型的异步方法称作任务返回。An async method returning a task type is said to be task-returning.

任务类型的确切定义是定义的实现,但从语言的角度来看,任务类型处于 "未完成"、"成功" 或 "出错" 状态之一。The exact definition of the task types is implementation defined, but from the language's point of view a task type is in one of the states incomplete, succeeded or faulted. 出错的任务记录相关的异常。A faulted task records a pertinent exception. 已成功 Task<T> 记录类型的结果 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 表达式 在其主体中) 。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 ,并且在调用时,异步函数调用的计算将从其停止的等待表达式中继续。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

调用任务返回的 async 函数会导致生成返回的任务类型的实例。Invocation of a task-returning async function causes an instance of the returned task type to be generated. 这称为 async 函数的 返回任务This is called the return task of the async function. 任务最初处于 "未完成" 状态。The task is initially in an incomplete state.

然后,将对异步函数体进行计算,直到它被挂起 (通过等待表达式) 或终止,此时,将控件返回给调用方,同时返回 task。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.

当 async 函数的主体终止时,返回的任务将不再处于 "未完成" 状态:When the body of the async function terminates, the return task is moved out of the incomplete state:

  • 如果函数体因到达 return 语句或正文末尾而终止,则返回的任务中会记录任何结果值,这会置于成功状态。If the function body terminates as the result of reaching a return statement or the end of the body, any result value is recorded in the return task, which is put into a succeeded state.
  • 如果函数体由于未捕获的异常的结果而终止 (throw 语句) 则在返回任务中记录异常,该异常会置于出错状态。If the function body terminates as the result of an uncaught exception (The throw statement) the exception is recorded in the return task which is put into a faulted state.

Void 返回的 async 函数的计算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.

这样,上下文就可以跟踪正在其下运行的空返回异步函数的数量,并决定如何传播它们发出的异常。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.