类Classes
类是一种数据结构,它可能包含 (常量和字段的数据成员) 、函数成员 (方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数) 以及嵌套类型。A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, destructors and static constructors), and nested types. 类类型支持继承,这是一个派生类可以扩展和专用化基类的机制。Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class.
类声明Class declarations
Class_declaration 是声明新类 (类型声明) type_declaration 。A class_declaration is a type_declaration (Type declarations) that declares a new class.
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier type_parameter_list?
class_base? type_parameter_constraints_clause* class_body ';'?
;
Class_declaration 包含一组可选的 特性 (特性) ,后跟一组可选的 class_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_list。A 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
internal
和 private
修饰符控制类的可访问性。The public
, protected
, internal
, and private
modifiers control the accessibility of the class. 根据出现类声明的上下文,某些修饰符可能不允许 (声明的可访问性) 。Depending on the context in which the class declaration occurs, some of these modifiers may not be permitted (Declared accessibility).
abstract
sealed
static
以下各节讨论了、和修饰符。The abstract
, sealed
and static
modifiers are discussed in the following sections.
抽象类Abstract classes
abstract
修饰符用于指示某个类不完整,并且它将仅用作一个基类。The abstract
modifier is used to indicate that a class is incomplete and that it is intended to be used only as a base class. 抽象类与非抽象类在以下方面有所不同:An abstract class differs from a non-abstract class in the following ways:
- 抽象类不能直接实例化,而是在抽象类上使用运算符的编译时错误
new
。An abstract class cannot be instantiated directly, and it is a compile-time error to use thenew
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 benull
or contain references to instances of non-abstract classes derived from the abstract types. - 允许使用抽象类 (但不需要抽象类) 包含抽象成员。An abstract class is permitted (but not required) to contain abstract members.
- 抽象类不能是密封的。An abstract class cannot be sealed.
如果非抽象类是从抽象类派生的,则非抽象类必须包含所有继承的抽象成员的实际实现,从而重写这些抽象成员。When a non-abstract class is derived from an abstract class, the non-abstract class must include actual implementations of all inherited abstract members, thereby overriding those abstract members. 示例中In the example
abstract class A
{
public abstract void F();
}
abstract class B: A
{
public void G() {}
}
class C: B
{
public override void F() {
// actual implementation of F
}
}
抽象类 A
引入抽象方法 F
。the abstract class A
introduces an abstract method F
. 类 B
引入了附加方法 G
,但由于它不提供的实现 F
,因此 B
还必须声明为抽象。Class B
introduces an additional method G
, but since it doesn't provide an implementation of F
, B
must also be declared abstract. 类将 C
重写 F
并提供实际实现。Class C
overrides F
and provides an actual implementation. 由于中没有抽象成员,因此 C
C
(允许但不需要) 为非抽象。Since there are no abstract members in C
, C
is permitted (but not required) to be non-abstract.
密封类Sealed classes
sealed
修饰符用于阻止从类派生。The sealed
modifier is used to prevent derivation from a class. 如果将密封类指定为其他类的基类,则会发生编译时错误。A compile-time error occurs if a sealed class is specified as the base class of another class.
密封类不能也是抽象类。A sealed class cannot also be an abstract class.
sealed
修饰符主要用于防止意外派生,但它还支持某些运行时优化。The sealed
modifier is primarily used to prevent unintended derivation, but it also enables certain run-time optimizations. 特别是,由于知道密封类绝不会有任何派生类,因此可以将密封类实例上的虚函数成员调用转换为非虚拟调用。In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.
静态类Static classes
static
修饰符用于标记声明为 静态类 的类。The static
modifier is used to mark the class being declared as a static class. 静态类不能被实例化,不能用作类型并且只能包含静态成员。A static class cannot be instantiated, cannot be used as a type and can contain only static members. 只有静态类可以包含扩展方法的声明 (扩展方法) 。Only a static class can contain declarations of extension methods (Extension methods).
静态类声明受到下列限制:A static class declaration is subject to the following restrictions:
- 静态类不能包含
sealed
或abstract
修饰符。A static class may not include asealed
orabstract
modifier. 但请注意,由于无法对静态类进行实例化或派生,因此它的行为就像它既是密封的又是抽象的。Note, however, that since a static class cannot be instantiated or derived from, it behaves as if it was both sealed and abstract. - 静态类不能包含 class_base 规范 (类基规范) ,并且无法显式指定基类或实现的接口列表。A static class may not include a class_base specification (Class base specification) and cannot explicitly specify a base class or a list of implemented interfaces. 静态类隐式继承自类型
object
。A static class implicitly inherits from typeobject
. - 静态类只能包含静态成员 () 静态成员 和实例成员 。A static class can only contain static members (Static and instance members). 请注意,常量和嵌套类型归类为静态成员。Note that constants and nested types are classified as static members.
- 静态类不能包含成员
protected
或protected internal
声明可访问性。A static class cannot have members withprotected
orprotected internal
declared accessibility.
这是一种编译时错误,违反其中的任何限制。It is a compile-time error to violate any of these restrictions.
静态类没有实例构造函数。A static class has no instance constructors. 不能在静态类中声明实例构造函数,也不能为静态类 (默认构造 函数) 提供默认的实例构造函数。It is not possible to declare an instance constructor in a static class, and no default instance constructor (Default constructors) is provided for a static class.
静态类的成员不是自动静态的,成员声明必须显式包含 static
修饰符 (除了常量和嵌套类型) 。The members of a static class are not automatically static, and the member declarations must explicitly include a static
modifier (except for constants and nested types). 当类嵌套在静态外部类中时,嵌套类不是静态类,除非它显式包含 static
修饰符。When a class is nested within a static outer class, the nested class is not a static class unless it explicitly includes a static
modifier.
引用静态类类型Referencing static class types
如果 namespace_or_type_name (命名空间和类型名称, 则允许) 引用静态类A namespace_or_type_name (Namespace and type names) is permitted to reference a static class if
- Namespace_or_type_name 是
T
窗体的 namespace_or_type_name 中的T.I
,或The namespace_or_type_name is theT
in a namespace_or_type_name of the formT.I
, or - Namespace_or_type_name
T
在 typeof_expression (参数中列出了窗体的 1)typeof(T)
。The namespace_or_type_name is theT
in a typeof_expression (Argument lists1) of the formtypeof(T)
.
如果 primary_expression (函数成员 ,则允许) 引用静态类A primary_expression (Function members) is permitted to reference a static class if
- Primary_expression
E
在 member_access (对窗体) 动态重载决策的编译时检查E.I
。The primary_expression is theE
in a member_access (Compile-time checking of dynamic overload resolution) of the formE.I
.
在其他任何上下文中,引用静态类会导致编译时错误。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 只列出接口类型,则假定直接基类为 object
。If a class declaration has no class_base, or if the class_base lists only interface types, the direct base class is assumed to be object
. 类从其直接基类继承成员,如 继承中所述。A class inherits members from its direct base class, as described in Inheritance.
示例中In the example
class A {}
class B: A {}
类称为的 A
直接基类 B
, B
被称为派生自 A
。class A
is said to be the direct base class of B
, and B
is said to be derived from A
. 由于 A
未显式指定直接基类,因此它的直接基类是隐式的 object
。Since A
does not explicitly specify a direct base class, its direct base class is implicitly object
.
对于构造类类型,如果在泛型类声明中指定了基类,则会通过将基类声明中的每个 type_parameter 替换为构造的类型的相应 type_argument 来获取构造类型的基类。For a constructed class type, if a base class is specified in the generic class declaration, the base class of the constructed type is obtained by substituting, for each type_parameter in the base class declaration, the corresponding type_argument of the constructed type. 给定泛型类声明Given the generic class declarations
class B<U,V> {...}
class G<T>: B<string,T[]> {...}
构造类型的基类 G<int>
为 B<string,int[]>
。the base class of the constructed type G<int>
would be B<string,int[]>
.
类类型的直接基类必须至少具有与类类型本身相同的可访问性, (可访问性域) 。The direct base class of a class type must be at least as accessible as the class type itself (Accessibility domains). 例如, public
类从或类派生的编译时错误 private
internal
。For example, it is a compile-time error for a public
class to derive from a private
or internal
class.
类类型的直接基类不得为以下任何类型: System.Array
、 System.Delegate
、 System.MulticastDelegate
、 System.Enum
或 System.ValueType
。The direct base class of a class type must not be any of the following types: System.Array
, System.Delegate
, System.MulticastDelegate
, System.Enum
, or System.ValueType
. 此外,泛型类声明不能 System.Attribute
用作直接或间接的基类。Furthermore, a generic class declaration cannot use System.Attribute
as a direct or indirect base class.
确定类的直接基类规范的含义时 A
B
,的直接基类 B
被暂时假定为 object
。While determining the meaning of the direct base class specification A
of a class B
, the direct base class of B
is temporarily assumed to be object
. 这可以确保基类规范的含义无法以递归方式依赖于自身。Intuitively this ensures that the meaning of a base class specification cannot recursively depend on itself. 示例:The example:
class A<T> {
public class B {}
}
class C : A<C.B> {}
存在错误,因为在基类规范中 A<C.B>
,的直接基类被 C
认为是 object
,因此,) 的 命名空间和类型名称 的规则 ( C
不被视为具有成员 B
。is in error since in the base class specification A<C.B>
the direct base class of C
is considered to be object
, and hence (by the rules of Namespace and type names) C
is not considered to have a member B
.
类类型的基类是直接基类及其基类。The base classes of a class type are the direct base class and its base classes. 换言之,基类集是直接基类关系的传递闭包。In other words, the set of base classes is the transitive closure of the direct base class relationship. 参考上面的示例,的基类 B
为 A
和 object
。Referring to the example above, the base classes of B
are A
and object
. 示例中In the example
class A {...}
class B<T>: A {...}
class C<T>: B<IComparable<T>> {...}
class D<T>: C<T[]> {...}
的基类 D<int>
为、、 C<int[]>
B<IComparable<int[]>>
A
和 object
。the base classes of D<int>
are C<int[]>
, B<IComparable<int[]>>
, A
, and object
.
除了类之外 object
,每个类类型都有一个直接基类。Except for class object
, every class type has exactly one direct base class. object
类没有直接基类,是所有其他类的最终基类。The object
class has no direct base class and is the ultimate base class of all other classes.
当类 B
派生自类时 A
,它是依赖的编译时错误 A
B
。When a class B
derives from a class A
, it is a compile-time error for A
to depend on B
. 类 直接依赖于 _ 其直接基类 (如果任何) 并 _直接依赖于*_ 在任何) 的情况,它会立即嵌套 (的类。A class directly depends on _ its direct base class (if any) and _directly depends on*_ the class within which it is immediately nested (if any). 根据此定义,类所依赖的完整类集是 _ 的反身和可传递闭包 直接依赖于* 关系。Given this definition, the complete set of classes upon which a class depends is the reflexive and transitive closure of the _ directly depends on* relationship.
示例The example
class A: A {}
是错误的,因为类依赖于自身。is erroneous because the class depends on itself. 同样,示例Likewise, the example
class A: B {}
class B: C {}
class C: A {}
出现错误,因为类循环依赖于自身。is in error because the classes circularly depend on themselves. 最后,示例Finally, the example
class A: B.C {}
class B: A
{
public class C {}
}
导致编译时错误,这是因为 A
依赖于 B.C
(其直接基类) ,这依赖于 B
(它的直接封闭类) ,后者会循环依赖 A
。results in a compile-time error because A
depends on B.C
(its direct base class), which depends on B
(its immediately enclosing class), which circularly depends on A
.
请注意,类不依赖于嵌套在其中的类。Note that a class does not depend on the classes that are nested within it. 示例中In the example
class A
{
class B: A {}
}
B
取决于 A
(A
,因为既是它的直接基类,也是它的直接封闭类) ,但不 A
依赖于 B
(,因为 B
既不是) 的基类,也不是的封闭类 A
。B
depends on A
(because A
is both its direct base class and its immediately enclosing class), but A
does not depend on B
(since B
is neither a base class nor an enclosing class of A
). 因此,该示例是有效的。Thus, the example is valid.
不能从 sealed
类派生。It is not possible to derive from a sealed
class. 示例中In the example
sealed class A {}
class B: A {} // Error, cannot derive from a sealed class
类 B
出错,因为它尝试从 sealed
类派生 A
。class B
is in error because it attempts to derive from the sealed
class A
.
接口实现Interface implementations
Class_base 规范可能包含一系列接口类型,在这种情况下,类可以直接实现给定的接口类型。A class_base specification may include a list of interface types, in which case the class is said to directly implement the given interface types. 接口实现中进一步讨论了接口实现。Interface implementations are discussed further in Interface implementations.
类型参数约束Type parameter constraints
泛型类型和方法声明可以选择通过包含 type_parameter_constraints_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
或 _值类型约束*_ struct
。A primary constraint can be a class type or the reference type constraint _ class
or the _value type constraint*_ struct
. 辅助约束可以是 _type_parameter * 或 interface_type。A secondary constraint can be a _type_parameter* or interface_type.
引用类型约束指定用于类型参数的类型参数必须是引用类型。The reference type constraint specifies that a type argument used for the type parameter must be a reference type. 下面定义的所有类类型、接口类型、委托类型、数组类型和类型参数 (如下所定义) 满足此约束。All class types, interface types, delegate types, array types, and type parameters known to be a reference type (as defined below) satisfy this constraint.
值类型约束指定用于类型参数的类型参数必须是不可以为 null 的值类型。The value type constraint specifies that a type argument used for the type parameter must be a non-nullable value type. 所有不可以为 null 的结构类型、枚举类型和具有值类型约束的类型参数均满足此约束。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_constraint。A type parameter having the value type constraint cannot also have the constructor_constraint.
指针类型永远不允许为类型参数,并且不被视为满足引用类型或值类型约束。Pointer types are never allowed to be type arguments and are not considered to satisfy either the reference type or value type constraints.
如果约束是类类型、接口类型或类型参数,则该类型指定用于该类型参数的每个类型参数都必须支持的最小 "基类型"。If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal "base type" that every type argument used for that type parameter must support. 当使用构造类型或泛型方法时,将在编译时对照类型参数上的约束检查类型参数。Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile-time. 提供的类型参数必须满足 满足约束中所述的条件。The type argument supplied must satisfy the conditions described in Satisfying constraints.
Class_type 约束必须满足以下规则:A class_type constraint must satisfy the following rules:
- 类型必须是类类型。The type must be a class type.
- 类型不得为
sealed
。The type must not besealed
. - 类型不得为以下类型之一:
System.Array
、System.Delegate
、System.Enum
或System.ValueType
。The type must not be one of the following types:System.Array
,System.Delegate
,System.Enum
, orSystem.ValueType
. - 类型不得为
object
。The type must not beobject
. 由于所有类型都派生自object
,因此如果允许,此类约束将不起作用。Because all types derive fromobject
, such a constraint would have no effect if it were permitted. - 给定类型参数最多只能有一个约束为类类型。At most one constraint for a given type parameter can be a class type.
指定为 interface_type 约束的类型必须满足以下规则:A type specified as an interface_type constraint must satisfy the following rules:
- 类型必须是接口类型。The type must be an interface type.
- 在给定子句中不能多次指定类型
where
。A type must not be specified more than once in a givenwhere
clause.
在任一情况下,约束都可以涉及关联的类型或方法声明的任何类型参数作为构造类型的一部分,并且可以涉及所声明的类型。In either case, the constraint can involve any of the type parameters of the associated type or method declaration as part of a constructed type, and can involve the type being declared.
任何指定为类型参数约束的类或接口类型都必须至少为可访问 (辅助功能约束) 为声明的泛型类型或方法。Any class or interface type specified as a type parameter constraint must be at least as accessible (Accessibility constraints) as the generic type or method being declared.
指定为 type_parameter 约束的类型必须满足以下规则:A type specified as a type_parameter constraint must satisfy the following rules:
- 类型必须为类型参数。The type must be a type parameter.
- 在给定子句中不能多次指定类型
where
。A type must not be specified more than once in a givenwhere
clause.
此外,类型参数的依赖项关系图中必须没有循环,其中依赖项是由定义的传递关系:In addition there must be no cycles in the dependency graph of type parameters, where dependency is a transitive relation defined by:
- 如果将类型参数用作
T
类型参数的约束,S
则S
依赖于T
。If a type parameterT
is used as a constraint for type parameterS
thenS
depends onT
. - 如果类型参数
S
依赖于类型参数T
并且T
依赖于类型参数,U
则S
依赖于U
。If a type parameterS
depends on a type parameterT
andT
depends on a type parameterU
thenS
depends onU
.
在给定此关系的情况下,类型参数依赖于自身 (直接或间接) 的编译时错误。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 soS
would be forced to be the same type asT
, eliminating the need for two type parameters.- 如果
S
具有值类型约束,则T
不能有 class_type 约束。IfS
has the value type constraint thenT
must not have a class_type constraint. - 如果
S
具有 class_type 约束A
,并且T
具有 class_type 约束,B
则必须存在从到的标识转换或隐式A
B
B
A
引用转换。IfS
has a class_type constraintA
andT
has a class_type constraintB
then there must be an identity conversion or implicit reference conversion fromA
toB
or an implicit reference conversion fromB
toA
. - 如果
S
还依赖于类型参数U
并且U
具有 class_type 约束A
,并且T
具有 class_type 约束,B
则必须存在从到的标识转换或隐式A
B
B
A
引用转换。IfS
also depends on type parameterU
andU
has a class_type constraintA
andT
has a class_type constraintB
then there must be an identity conversion or implicit reference conversion fromA
toB
or an implicit reference conversion fromB
toA
.
S
具有值类型约束并 T
具有引用类型约束的有效方法。It is valid for S
to have the value type constraint and T
to have the reference type constraint. T
对类型 System.Object
、 System.ValueType
、 System.Enum
和接口类型有效地限制了这一限制。Effectively this limits T
to the types System.Object
, System.ValueType
, System.Enum
, and any interface type.
如果 where
类型参数的子句包括) 具有形式的构造函数约束 (,则可以 new()
使用 new
运算符来创建 (对象创建表达式) 的实例。If the where
clause for a type parameter includes a constructor constraint (which has the form new()
), it is possible to use the new
operator to create instances of the type (Object creation expressions). 用于具有构造函数约束的类型参数的任何类型参数都必须具有一个公共的无参数构造函数 (此构造函数对于任何值类型都是隐式存在的) 或者是具有值类型约束或构造函数约束的类型参数 (请参阅 类型参数约束 以获取详细信息) 。Any type argument used for a type parameter with a constructor constraint must have a public parameterless constructor (this constructor implicitly exists for any value type) or be a type parameter having the value type constraint or constructor constraint (see Type parameter constraints for details).
下面是约束的示例:The following are examples of constraints:
interface IPrintable
{
void Print();
}
interface IComparable<T>
{
int CompareTo(T value);
}
interface IKeyProvider<T>
{
T GetKey();
}
class Printer<T> where T: IPrintable {...}
class SortedList<T> where T: IComparable<T> {...}
class Dictionary<K,V>
where K: IComparable<K>
where V: IPrintable, IKeyProvider<K>, new()
{
...
}
下面的示例出错,因为它在类型参数的依赖项关系图中导致循环:The following example is in error because it causes a circularity in the dependency graph of the type parameters:
class Circular<S,T>
where S: T
where T: S // Error, circularity in dependency graph
{
...
}
下面的示例演示了其他无效情况:The following examples illustrate additional invalid situations:
class Sealed<S,T>
where S: T
where T: struct // Error, T is sealed
{
...
}
class A {...}
class B {...}
class Incompat<S,T>
where S: A, T
where T: B // Error, incompatible class-type constraints
{
...
}
class StructWithClass<S,T,U>
where S: struct, T
where T: U
where U: A // Error, A incompatible with struct
{
...
}
类型参数的 有效基类 T
定义如下:The effective base class of a type parameter T
is defined as follows:
- 如果
T
没有主约束或类型参数约束,则其有效基类为object
。IfT
has no primary constraints or type parameter constraints, its effective base class isobject
. - 如果
T
具有值类型约束,则其有效基类为System.ValueType
。IfT
has the value type constraint, its effective base class isSystem.ValueType
. - 如果
T
具有 class_type 约束C
但没有 type_parameter 约束,则其有效基类为C
。IfT
has a class_type constraintC
but no type_parameter constraints, its effective base class isC
. - 如果
T
没有 class_type 约束,但具有一个或多 个 type_parameter 约束,则其有效基类是在其 type_parameter 约束的一组有效基类中) 的包含程度最高的类型 (提升转换运算符。IfT
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 约束的有效基类的集中。IfT
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 ofT
and the effective base classes of its type_parameter constraints. 一致性规则确保存在这种包含最多的类型。The consistency rules ensure that such a most encompassed type exists. - 如果
T
具有引用类型约束但没有 class_type 约束,则其有效基类为object
。IfT
has the reference type constraint but no class_type constraints, its effective base class isobject
.
对于这些规则,如果 T 具有 value_type 的约束,则 V
改用的最特定基类型 V
为 class_type。For the purpose of these rules, if T has a constraint V
that is a value_type, use instead the most specific base type of V
that is a class_type. 此操作永远不会在显式给定的约束中发生,但当通过重写方法声明或接口方法的显式实现隐式继承泛型方法时,可能会出现这种情况。This can never happen in an explicitly given constraint, but may occur when the constraints of a generic method are implicitly inherited by an overriding method declaration or an explicit implementation of an interface method.
这些规则确保有效的基类始终是 class_type。These rules ensure that the effective base class is always a class_type.
类型参数的 有效接口集 T
定义如下:The effective interface set of a type parameter T
is defined as follows:
- 如果
T
没有 secondary_constraints,则其有效接口集为空。IfT
has no secondary_constraints, its effective interface set is empty. - 如果
T
具有 interface_type 约束但没有 type_parameter 约束,则其有效接口集是其 interface_type 约束的集合。IfT
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 约束的有效接口集的并集。IfT
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 约束的有效接口集的并集。IfT
has both interface_type constraints and type_parameter constraints, its effective interface set is the union of its set of interface_type constraints and the effective interface sets of its type_parameter constraints.
如果类型参数具有引用类型约束,或者其有效的基类不是或,则已知该类型参数是 引用类型 object
System.ValueType
。A type parameter is known to be a reference type if it has the reference type constraint or its effective base class is not object
or System.ValueType
.
受约束的类型参数类型的值可用于访问约束隐含的实例成员。Values of a constrained type parameter type can be used to access the instance members implied by the constraints. 示例中In the example
interface IPrintable
{
void Print();
}
class Printer<T> where T: IPrintable
{
void PrintOne(T x) {
x.Print();
}
}
IPrintable
可以直接在上调用方法, x
因为 T
被约束为始终实现 IPrintable
。the methods of IPrintable
can be invoked directly on x
because T
is constrained to always implement IPrintable
.
类体Class body
类的 class_body 定义该类的成员。The class_body of a class defines the members of that class.
class_body
: '{' class_member_declaration* '}'
;
分部类型Partial types
类型声明可跨多个 分部类型声明 拆分。A type declaration can be split across multiple partial type declarations. 类型声明是根据此部分中的规则从其部分构造的,因此在程序的编译时和运行时处理的其余部分,将它视为单个声明。The type declaration is constructed from its parts by following the rules in this section, whereupon it is treated as a single declaration during the remainder of the compile-time and run-time processing of the program.
如果包含修饰符,则 class_declaration、 struct_declaration 或 interface_declaration 表示分部类型声明 partial
。A class_declaration, struct_declaration or interface_declaration represents a partial type declaration if it includes a partial
modifier. partial
不是关键字,并且仅当它紧靠在某个关键字之前 class
、 struct
interface
类型声明中或 void
方法声明中的类型之前出现时,才作为修饰符。partial
is not a keyword, and only acts as a modifier if it appears immediately before one of the keywords class
, struct
or interface
in a type declaration, or before the type void
in a method declaration. 在其他上下文中,可以将其用作常规标识符。In other contexts it can be used as a normal identifier.
分部类型声明的每个部分都必须包含 partial
修饰符。Each part of a partial type declaration must include a partial
modifier. 它必须具有相同的名称,并在与其他部分相同的命名空间或类型声明中声明。It must have the same name and be declared in the same namespace or type declaration as the other parts. partial
修饰符指示类型声明的其他部分可能存在于其他位置,但不要求存在此类附加部分; 对于具有单个声明的类型,此类型对于包含修饰符的类型是有效的 partial
。The partial
modifier indicates that additional parts of the type declaration may exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for a type with a single declaration to include the partial
modifier.
分部类型的所有部分都必须一起编译,以便可以在编译时将这些部分合并为单个类型声明。All parts of a partial type must be compiled together such that the parts can be merged at compile-time into a single type declaration. 具体而言,分部类型不允许扩展已编译的类型。Partial types specifically do not allow already compiled types to be extended.
可以通过使用修饰符在多个部分中声明嵌套类型 partial
。Nested types may be declared in multiple parts by using the partial
modifier. 通常,包含类型是使用声明的 partial
,并且嵌套类型的每个部分都是在包含类型的不同部分中声明的。Typically, the containing type is declared using partial
as well, and each part of the nested type is declared in a different part of the containing type.
partial
委托或枚举声明上不允许使用修饰符。The partial
modifier is not permitted on delegate or enum declarations.
属性Attributes
分部类型的属性是通过将每个部件的属性按未指定顺序组合来确定的。The attributes of a partial type are determined by combining, in an unspecified order, the attributes of each of the parts. 如果特性放置在多个部件上,则等效于在类型上多次指定属性。If an attribute is placed on multiple parts, it is equivalent to specifying the attribute multiple times on the type. 例如,这两个部分:For example, the two parts:
[Attr1, Attr2("hello")]
partial class A {}
[Attr3, Attr2("goodbye")]
partial class A {}
等效于如下所示的声明:are equivalent to a declaration such as:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}
类型参数上的属性以类似的方式进行组合。Attributes on type parameters combine in a similar fashion.
修饰符Modifiers
当分部类型声明包含 (、、和修饰符的辅助功能规范时 public
protected
internal
private
) 它必须与包括辅助功能规范的所有其他部分协商。When a partial type declaration includes an accessibility specification (the public
, protected
, internal
, and private
modifiers) it must agree with all other parts that include an accessibility specification. 如果分部类型的任何部分都不包括辅助功能规范,则会为该类型提供适当的默认辅助功能 (声明的可访问性) 。If no part of a partial type includes an accessibility specification, the type is given the appropriate default accessibility (Declared accessibility).
如果嵌套类型的一个或多个分部声明包含 new
修饰符,则如果嵌套类型隐藏继承成员 (通过继承) 隐藏 该成员,则不会报告警告。If one or more partial declarations of a nested type include a new
modifier, no warning is reported if the nested type hides an inherited member (Hiding through inheritance).
如果类的一个或多个分部声明包含 abstract
修饰符,则将类视为抽象类 (抽象类) 。If one or more partial declarations of a class include an abstract
modifier, the class is considered abstract (Abstract classes). 否则,类被视为非抽象类。Otherwise, the class is considered non-abstract.
如果某个类的一个或多个分部声明包含 sealed
修饰符,则该类被视为密封 (密封类) 。If one or more partial declarations of a class include a sealed
modifier, the class is considered sealed (Sealed classes). 否则,类被视为未密封。Otherwise, the class is considered unsealed.
请注意,类不能既是抽象的又是密封的。Note that a class cannot be both abstract and sealed.
当 unsafe
修饰符用于分部类型声明时,只有该特定部分被视为不安全的上下文 (不安全 上下文) 。When the unsafe
modifier is used on a partial type declaration, only that particular part is considered an unsafe context (Unsafe contexts).
类型参数和约束Type parameters and constraints
如果泛型类型在多个部分中声明,则每个部分都必须声明类型参数。If a generic type is declared in multiple parts, each part must state the type parameters. 每个部分必须具有相同数量的类型参数,并且每个类型参数的名称必须相同。Each part must have the same number of type parameters, and the same name for each type parameter, in order.
当部分泛型类型声明包括) (子句约束时 where
,约束必须与包含约束的所有其他部分一致。When a partial generic type declaration includes constraints (where
clauses), the constraints must agree with all other parts that include constraints. 具体而言,包括约束的每个部分都必须具有对同一组类型参数的约束,并且对于每个类型参数,主要、次要和构造函数约束的集必须是等效的。Specifically, each part that includes constraints must have constraints for the same set of type parameters, and for each type parameter the sets of primary, secondary, and constructor constraints must be equivalent. 如果两组约束包含相同的成员,则它们是等效的。Two sets of constraints are equivalent if they contain the same members. 如果部分泛型类型的任何部分都不指定类型参数约束,则类型参数被视为不受约束。If no part of a partial generic type specifies type parameter constraints, the type parameters are considered unconstrained.
示例The example
partial class Dictionary<K,V>
where K: IComparable<K>
where V: IKeyProvider<K>, IPersistable
{
...
}
partial class Dictionary<K,V>
where V: IPersistable, IKeyProvider<K>
where K: IComparable<K>
{
...
}
partial class Dictionary<K,V>
{
...
}
是正确的,因为包含约束的那些部分 (第一个) 有效地为同一组类型参数指定相同的一组主、辅助和构造函数约束。is correct because those parts that include constraints (the first two) effectively specify the same set of primary, secondary, and constructor constraints for the same set of type parameters, respectively.
基类Base class
当分部类声明包含基类规范时,它必须与包含基类规范的所有其他部分协商。When a partial class declaration includes a base class specification it must agree with all other parts that include a base class specification. 如果分部类的任何部分都不包含基类规范,则基类会成为 System.Object
) (基类 。If no part of a partial class includes a base class specification, the base class becomes System.Object
(Base classes).
基接口Base interfaces
在多个部分中声明的类型的基接口集是在每个部件上指定的基本接口的联合。The set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. 特定基接口在每个部分只能命名一次,但允许多个部件命名 () 的相同基接口。A particular base interface may only be named once on each part, but it is permitted for multiple parts to name the same base interface(s). 任何给定基接口的成员只能有一个实现。There must only be one implementation of the members of any given base interface.
示例中In the example
partial class C: IA, IB {...}
partial class C: IC {...}
partial class C: IA, IB {...}
类的基接口集 C
为 IA
、 IB
和 IC
。the set of base interfaces for class C
is IA
, IB
, and IC
.
通常,每个部分都提供了接口 (的实现) 在该部件上声明;但这并不是必需的。Typically, each part provides an implementation of the interface(s) declared on that part; however, this is not a requirement. 部件可以为在不同的部分声明的接口提供实现:A part may provide the implementation for an interface declared on a different part:
partial class X
{
int IComparable.CompareTo(object o) {...}
}
partial class X: IComparable
{
...
}
成员Members
除了分部方法) (分部方法 以外,在多个部分中声明的类型成员集只是每个部分中声明的成员集的并集。With the exception of partial methods (Partial methods), the set of members of a type declared in multiple parts is simply the union of the set of members declared in each part. 类型声明的所有部分的主体) (声明 中共享相同的声明空间,并且 (范围 的每个成员的范围) 会延伸到所有部分的主体。The bodies of all parts of the type declaration share the same declaration space (Declarations), and the scope of each member (Scopes) extends to the bodies of all the parts. 任何成员的可访问域始终包括封闭类型的所有部分; private
在一个部分中声明的成员可从另一个部件自由地访问。The accessibility domain of any member always includes all the parts of the enclosing type; a private
member declared in one part is freely accessible from another part. 在该类型的多个部分中声明同一成员是编译时错误,除非该成员是带有修饰符的类型 partial
。It is a compile-time error to declare the same member in more than one part of the type, unless that member is a type with the partial
modifier.
partial class A
{
int x; // Error, cannot declare x more than once
partial class Inner // Ok, Inner is a partial type
{
int y;
}
}
partial class A
{
int x; // Error, cannot declare x more than once
partial class Inner // Ok, Inner is a partial type
{
int z;
}
}
类型成员在 c # 代码中的排序很少重要,但在与其他语言和环境交互时可能很重要。The ordering of members within a type is rarely significant to C# code, but may be significant when interfacing with other languages and environments. 在这些情况下,在多个部分中声明的类型中的成员顺序是不确定的。In these cases, the ordering of members within a type declared in multiple parts is undefined.
分部方法Partial methods
分部方法可在类型声明的一部分中定义,并在另一个中实现。Partial methods can be defined in one part of a type declaration and implemented in another. 实现是可选的;如果没有部分实现分部方法,则将从部分组合生成的类型声明中删除分部方法声明和对它的所有调用。The implementation is optional; if no part implements the partial method, the partial method declaration and all calls to it are removed from the type declaration resulting from the combination of the parts.
分部方法不能定义访问修饰符,而是隐式的 private
。Partial methods cannot define access modifiers, but are implicitly private
. 它们的返回类型必须是 void
,并且其参数不能具有 out
修饰符。Their return type must be void
, and their parameters cannot have the out
modifier. partial
仅当标识符在类型前显示时,才会将标识符识别为方法声明中的特殊关键字 void
; 否则,它可用作普通标识符。The identifier partial
is recognized as a special keyword in a method declaration only if it appears right before the void
type; otherwise it can be used as a normal identifier. 分部方法不能显式实现接口方法。A partial method cannot explicitly implement interface methods.
分部方法声明的类型有两种:如果方法声明的主体为分号,则声明为 *定义分部方法声明 _。There are two kinds of partial method declarations: If the body of the method declaration is a semicolon, the declaration is said to be a *defining partial method declaration _. 如果主体被指定为 _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
不包括修饰符Thepartial
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:
- 创建委托给方法 (委托创建表达式) ,这是编译时错误。It is a compile-time error to create a delegate to method (Delegate creation expressions).
- 在
M
转换为表达式树类型的匿名函数内引用 (编译时错误,该函数将 对表达式树类型的匿名函数转换进行计算) 。It is a compile-time error to refer toM
inside an anonymous function that is converted to an expression tree type (Evaluation of anonymous function conversions to expression tree types). - 作为调用的一部分出现的表达式
M
不会影响明确赋值) (明确赋值 状态,这可能会导致编译时错误。Expressions occurring as part of an invocation ofM
do not affect the definite assignment state (Definite assignment), which can potentially lead to compile-time errors. M
不能是应用程序 (应用程序启动) 的入口点。M
cannot be the entry point for an application (Application Startup).
分部方法有助于允许类型声明的一部分自定义另一个部件的行为,例如,由工具生成的部分。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_declaration:The 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
和不同out
。In addition, the signature (Signatures and overloading) of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely byref
andout
. - 实例构造函数的签名必须不同于同一个类中声明的所有其他实例构造函数的签名,同一类中声明的两个构造函数的签名可能并不完全不同于
ref
和out
。The signature of an instance constructor must differ from the signatures of all other instance constructors declared in the same class, and two constructors declared in the same class may not have signatures that differ solely byref
andout
. - 索引器的签名必须与同一类中声明的所有其他索引器的签名不同。The signature of an indexer must differ from the signatures of all other indexers declared in the same class.
- 运算符的签名必须与同一类中声明的所有其他运算符的签名不同。The signature of an operator must differ from the signatures of all other operators declared in the same class.
类 类型 (继承 成员) 不属于类的声明空间。The inherited members of a class type (Inheritance) are not part of the declaration space of a class. 因此,允许派生类声明与继承成员具有相同名称或签名的成员 (这实际上会隐藏继承的成员) 。Thus, a derived class is allowed to declare a member with the same name or signature as an inherited member (which in effect hides the inherited member).
实例类型The instance type
每个类声明都具有关联的绑定类型 (绑定类型和未绑定类型) 实例类型。Each class declaration has an associated bound type (Bound and unbound types), the instance type. 对于泛型类声明,实例类型是通过从类型声明 创建 (构造类型) 构造类型生成的,其中每个提供的类型参数都是对应的类型参数。For a generic class declaration, the instance type is formed by creating a constructed type (Constructed types) from the type declaration, with each of the supplied type arguments being the corresponding type parameter. 由于实例类型使用类型参数,因此只能在类型参数位于范围内时使用。也就是说,在类声明中。Since the instance type uses the type parameters, it can only be used where the type parameters are in scope; that is, inside the class declaration. 实例类型是在 this
类声明中编写的代码的类型。The instance type is the type of this
for code written inside the class declaration. 对于非泛型类,实例类型只是声明的类。For non-generic classes, the instance type is simply the declared class. 下面显示了多个类声明及其实例类型:The following shows several class declarations along with their instance types:
class A<T> // instance type: A<T>
{
class B {} // instance type: A<T>.B
class C<U> {} // instance type: A<T>.C<U>
}
class D {} // instance type: D
构造类型的成员Members of constructed types
构造类型的非继承成员是通过将成员声明中的每个 type_parameter 替换为构造类型的对应 type_argument 来获取的。The non-inherited members of a constructed type are obtained by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the constructed type. 替换过程基于类型声明的语义含义,而不只是文本替换。The substitution process is based on the semantic meaning of type declarations, and is not simply textual substitution.
例如,给定泛型类声明For example, given the generic class declaration
class Gen<T,U>
{
public T[,] a;
public void G(int i, T t, Gen<U,T> gt) {...}
public U Prop { get {...} set {...} }
public int H(double d) {...}
}
构造的类型 Gen<int[],IComparable<string>>
具有以下成员:the constructed type Gen<int[],IComparable<string>>
has the following members:
public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}
a
泛型类声明中的成员类型 Gen
为 "二维数组 T
",因此 a
以上构造类型中的成员类型为 "、或的一维数组的二维数组 int
int[,][]
。The type of the member a
in the generic class declaration Gen
is "two-dimensional array of T
", so the type of the member a
in the constructed type above is "two-dimensional array of one-dimensional array of int
", or int[,][]
.
在实例函数成员内,的类型 this
是实例类型 (包含声明的 实例类型) 。Within instance function members, the type of this
is the instance type (The instance type) of the containing declaration.
泛型类的所有成员都可以直接或作为构造类型的一部分,使用任何封闭类中的类型参数。All members of a generic class can use type parameters from any enclosing class, either directly or as part of a constructed type. 当) 在运行时使用特定的封闭式构造类型 (打开和关闭 的类型时,将使用为构造类型提供的实际类型参数替换类型参数的每个使用情况。When a particular closed constructed type (Open and closed types) is used at run-time, each use of a type parameter is replaced with the actual type argument supplied to the constructed type. 例如:For example:
class C<V>
{
public V f1;
public C<V> f2 = null;
public C(V x) {
this.f1 = x;
this.f2 = this;
}
}
class Application
{
static void Main() {
C<int> x1 = new C<int>(1);
Console.WriteLine(x1.f1); // Prints 1
C<double> x2 = new C<double>(3.1415);
Console.WriteLine(x2.f1); // Prints 3.1415
}
}
继承Inheritance
类 继承 其直接基类类型的成员。A class inherits the members of its direct base class type. 继承意味着类隐式包含其直接基类类型的所有成员,基类的实例构造函数、析构函数和静态构造函数除外。Inheritance means that a class implicitly contains all members of its direct base class type, except for the instance constructors, destructors and static constructors of the base class. 继承的一些重要方面包括:Some important aspects of inheritance are:
- 继承是可传递的。Inheritance is transitive. 如果
C
派生自B
,并且B
派生自A
,则C
继承在中声明的成员以及B
在中声明的成员A
。IfC
is derived fromB
, andB
is derived fromA
, thenC
inherits the members declared inB
as well as the members declared inA
. - 派生类扩展其直接基类。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_argument 。These members, in turn, are transformed by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the class_base specification.
class B<U>
{
public U F(long index) {...}
}
class D<T>: B<T[]>
{
public T G(string s) {...}
}
在上面的示例中,构造类型 D<int>
具有 public int G(string s)
通过替换类型参数的类型参数获得的非继承成员 int
T
。In the above example, the constructed type D<int>
has a non-inherited member public int G(string s)
obtained by substituting the type argument int
for the type parameter T
. D<int>
还具有类声明中的继承成员 B
。D<int>
also has an inherited member from the class declaration B
. 此继承成员是通过先 B<int[]>
D<int>
通过 int
T
在基类规范中替换为 B<T[]>
来确定的基类类型确定的。This inherited member is determined by first determining the base class type B<int[]>
of D<int>
by substituting int
for T
in the base class specification B<T[]>
. 然后,将替换为中的类型自变量,从而 B
int[]
U
public U F(long index)
生成继承成员 public int[] F(long index)
。Then, as a type argument to B
, int[]
is substituted for U
in public U F(long index)
, yielding the inherited member public int[] F(long index)
.
New 修饰符The new modifier
允许 class_member_declaration 声明与继承成员具有相同名称或签名的成员。A class_member_declaration is permitted to declare a member with the same name or signature as an inherited member. 出现这种情况时,会说派生类成员 隐藏 基类成员。When this occurs, the derived class member is said to hide the base class member. 隐藏继承成员不被视为错误,但会导致编译器发出警告。Hiding an inherited member is not considered an error, but it does cause the compiler to issue a warning. 若要禁止显示该警告,派生类成员的声明可以包含 new
修饰符以指示该派生成员用于隐藏基成员。To suppress the warning, the declaration of the derived class member can include a new
modifier to indicate that the derived member is intended to hide the base member. 本主题将在 通过继承隐藏中进一步进行讨论。This topic is discussed further in Hiding through inheritance.
如果 new
修饰符包含在不隐藏继承成员的声明中,则会发出对该结果的警告。If a new
modifier is included in a declaration that doesn't hide an inherited member, a warning to that effect is issued. 通过删除修饰符,可取消显示此警告 new
。This warning is suppressed by removing the new
modifier.
访问修饰符Access modifiers
Class_member_declaration 可以有五种可能类型的声明的可访问性 (中 声明的可访问性) : public
、 protected internal
、 protected
、 internal
或 private
。A class_member_declaration can have any one of the five possible kinds of declared accessibility (Declared accessibility): public
, protected internal
, protected
, internal
, or private
. 除了组合外 protected internal
,还可以指定多个访问修饰符,这是编译时错误。Except for the protected internal
combination, it is a compile-time error to specify more than one access modifier. 如果 class_member_declaration 不包含任何访问修饰符, private
则采用。When a class_member_declaration does not include any access modifiers, private
is assumed.
构成类型Constituent types
在成员的声明中使用的类型称为成员的构成类型。Types that are used in the declaration of a member are called the constituent types of that member. 可能的构成类型为常量、字段、属性、事件或索引器的类型、方法或运算符的返回类型,以及方法、索引器、运算符或实例构造函数的参数类型。Possible constituent types are the type of a constant, field, property, event, or indexer, the return type of a method or operator, and the parameter types of a method, indexer, operator, or instance constructor. 成员的构成类型必须至少与该成员本身相同 (可访问 性约束) 。The constituent types of a member must be at least as accessible as that member itself (Accessibility constraints).
静态成员和实例成员Static and instance members
类的成员为 *静态成员 _ 或 _ 实例成员 *。Members of a class are either static members _ or _instance members**. 一般来说,将静态成员视为属于类类型,并将实例成员视为属于类类型 (实例) 的对象。Generally speaking, it is useful to think of static members as belonging to class types and instance members as belonging to objects (instances of class types).
当字段、方法、属性、事件、运算符或构造函数声明包含修饰符时 static
,它将声明一个静态成员。When a field, method, property, event, operator, or constructor declaration includes a static
modifier, it declares a static member. 此外,常数或类型声明隐式声明静态成员。In addition, a constant or type declaration implicitly declares a static member. 静态成员具有以下特征:Static members have the following characteristics:
M
在窗体的 Member_access (成员访问) 中引用静态成员时E.M
,E
必须表示包含的类型M
。When a static memberM
is referenced in a member_access (Member access) of the formE.M
,E
must denote a type containingM
. 表示实例的编译时错误E
。It is a compile-time error forE
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 tothis
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.M
,E
必须表示包含的类型的实例M
。When an instance memberM
is referenced in a member_access (Member access) of the formE.M
,E
must denote an instance of a type containingM
. 它是的绑定时错误,它E
表示类型。It is a binding-time error forE
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 asthis
(This access).
下面的示例演示用于访问静态成员和实例成员的规则:The following example illustrates the rules for accessing static and instance members:
class Test
{
int x;
static int y;
void F() {
x = 1; // Ok, same as this.x = 1
y = 1; // Ok, same as Test.y = 1
}
static void G() {
x = 1; // Error, cannot access this.x
y = 1; // Ok, same as Test.y = 1
}
static void Main() {
Test t = new Test();
t.x = 1; // Ok
t.y = 1; // Error, cannot access static member through instance
Test.x = 1; // Error, cannot access instance member through type
Test.y = 1; // Ok
}
}
F
方法显示在实例函数成员中, Simple_name (简单名称) 可用于访问实例成员和静态成员。The F
method shows that in an instance function member, a simple_name (Simple names) can be used to access both instance members and static members. G
方法表明,在静态函数成员中,通过 simple_name 访问实例成员是编译时错误。The G
method shows that in a static function member, it is a compile-time error to access an instance member through a simple_name. Main
方法表明,在 Member_access (成员访问) ,必须通过实例访问实例成员,并且必须通过类型访问静态成员。The Main
method shows that in a member_access (Member access), instance members must be accessed through instances, and static members must be accessed through types.
嵌套类型Nested types
在类或结构声明中声明的类型称为 *嵌套类型 _。A type declared within a class or struct declaration is called a *nested type _. 在编译单元或命名空间内声明的类型称为 _ 非嵌套类型 *。A type that is declared within a compilation unit or namespace is called a _*non-nested type**.
示例中In the example
using System;
class A
{
class B
{
static void F() {
Console.WriteLine("A.B.F");
}
}
}
类 B
是嵌套类型,因为它是在类中声明的 A
,而类 A
是非嵌套类型,因为它是在编译单元中声明的。class B
is a nested type because it is declared within class A
, and class A
is a non-nested type because it is declared within a compilation unit.
完全限定的名称Fully qualified name
嵌套 类型 (完全限定名) 的完全限定名是 S.N
,其中 S
是声明类型的类型的完全限定名称 N
。The fully qualified name (Fully qualified names) for a nested type is S.N
where S
is the fully qualified name of the type in which type N
is declared.
声明的可访问性Declared accessibility
非嵌套类型可以具有 public
或 internal
声明可访问性,并且 internal
默认情况下已声明可访问性。Non-nested types can have public
or internal
declared accessibility and have internal
declared accessibility by default. 嵌套类型也可以具有这些形式的声明的可访问性,还可以包含一个或多个声明的可访问性的其他形式,具体取决于包含类型是否为类或结构:Nested types can have these forms of declared accessibility too, plus one or more additional forms of declared accessibility, depending on whether the containing type is a class or struct:
- 在类中声明的嵌套类型可以有五种形式的声明的可访问性 (
public
、protected internal
、protected
、internal
或) ,与private
其他类成员一样,默认为已private
声明的可访问性。A nested type that is declared in a class can have any of five forms of declared accessibility (public
,protected internal
,protected
,internal
, orprivate
) and, like other class members, defaults toprivate
declared accessibility. - 在结构中声明的嵌套类型可以有三种形式的声明的可访问性 (
public
、internal
或private
) ,而与其他结构成员一样,默认为已private
声明的可访问性。A nested type that is declared in a struct can have any of three forms of declared accessibility (public
,internal
, orprivate
) and, like other struct members, defaults toprivate
declared accessibility.
示例The example
public class List
{
// Private data structure
private class Node
{
public object Data;
public Node Next;
public Node(object data, Node next) {
this.Data = data;
this.Next = next;
}
}
private Node first = null;
private Node last = null;
// Public interface
public void AddToFront(object o) {...}
public void AddToBack(object o) {...}
public object RemoveFromFront() {...}
public object RemoveFromBack() {...}
public int Count { get {...} }
}
声明私有嵌套类 Node
。declares a private nested class Node
.
遮盖Hiding
嵌套类型可能会隐藏基成员) (名称隐藏 。A nested type may hide (Name hiding) a base member. new
允许对嵌套类型声明使用修饰符,以便可以显式表达隐藏。The new
modifier is permitted on nested type declarations so that hiding can be expressed explicitly. 示例The example
using System;
class Base
{
public static void M() {
Console.WriteLine("Base.M");
}
}
class Derived: Base
{
new public class M
{
public static void F() {
Console.WriteLine("Derived.M.F");
}
}
}
class Test
{
static void Main() {
Derived.M.F();
}
}
显示 M
隐藏中定义的方法的嵌套类 M
Base
。shows a nested class M
that hides the method M
defined in Base
.
此访问this access
嵌套类型和其包含类型与 此访问) (的 this_access 无关。A nested type and its containing type do not have a special relationship with regard to this_access (This access). 具体而言, this
在嵌套类型中不能用于引用包含类型的实例成员。Specifically, this
within a nested type cannot be used to refer to instance members of the containing type. 在嵌套类型需要访问其包含类型的实例成员的情况下,可以通过为 this
包含类型的实例提供作为嵌套类型的构造函数参数来提供访问。In cases where a nested type needs access to the instance members of its containing type, access can be provided by providing the this
for the instance of the containing type as a constructor argument for the nested type. 下面为示例The following example
using System;
class C
{
int i = 123;
public void F() {
Nested n = new Nested(this);
n.G();
}
public class Nested
{
C this_c;
public Nested(C c) {
this_c = c;
}
public void G() {
Console.WriteLine(this_c.i);
}
}
}
class Test
{
static void Main() {
C c = new C();
c.F();
}
}
显示此方法。shows this technique. 的实例会 C
创建的实例 Nested
,并将其自己 this
Nested
的构造函数传递给的构造函数,以便提供对 C
实例成员的后续访问。An instance of C
creates an instance of Nested
and passes its own this
to Nested
's constructor in order to provide subsequent access to C
's instance members.
访问包含类型的私有和受保护成员Access to private and protected members of the containing type
嵌套类型可以访问其包含类型可以访问的所有成员,包括具有 private
和已 protected
声明可访问性的包含类型的成员。A nested type has access to all of the members that are accessible to its containing type, including members of the containing type that have private
and protected
declared accessibility. 示例The example
using System;
class C
{
private static void F() {
Console.WriteLine("C.F");
}
public class Nested
{
public static void G() {
F();
}
}
}
class Test
{
static void Main() {
C.Nested.G();
}
}
显示 C
包含嵌套类的类 Nested
。shows a class C
that contains a nested class Nested
. 在中 Nested
,方法 G
调用 F
中定义的静态方法 C
,并 F
具有私有声明的可访问性。Within Nested
, the method G
calls the static method F
defined in C
, and F
has private declared accessibility.
嵌套类型还可以访问在其包含类型的基类型中定义的受保护成员。A nested type also may access protected members defined in a base type of its containing type. 示例中In the example
using System;
class Base
{
protected void F() {
Console.WriteLine("Base.F");
}
}
class Derived: Base
{
public class Nested
{
public void G() {
Derived d = new Derived();
d.F(); // ok
}
}
}
class Test
{
static void Main() {
Derived.Nested n = new Derived.Nested();
n.G();
}
}
嵌套类 Derived.Nested
F
Derived
Base
通过调用实例来访问在的基类中定义的受保护方法 Derived
。the nested class Derived.Nested
accesses the protected method F
defined in Derived
's base class, Base
, by calling through an instance of Derived
.
泛型类中的嵌套类型Nested types in generic classes
泛型类声明可以包含嵌套类型声明。A generic class declaration can contain nested type declarations. 可以在嵌套类型中使用封闭类的类型参数。The type parameters of the enclosing class can be used within the nested types. 嵌套类型声明可以包含其他仅适用于嵌套类型的类型参数。A nested type declaration can contain additional type parameters that apply only to the nested type.
泛型类声明中包含的每个类型声明都是隐式的泛型类型声明。Every type declaration contained within a generic class declaration is implicitly a generic type declaration. 写入嵌套在泛型类型中的类型的引用时,必须将包含构造类型(包括其类型参数)命名为。When writing a reference to a type nested within a generic type, the containing constructed type, including its type arguments, must be named. 但是,从外部类中可以使用嵌套类型而无需进行限定;构造嵌套类型时,可以隐式使用外部类的实例类型。However, from within the outer class, the nested type can be used without qualification; the instance type of the outer class can be implicitly used when constructing the nested type. 下面的示例演示了三种不同的方法来引用创建的构造类型 Inner
; 前两种方法是等效的:The following example shows three different correct ways to refer to a constructed type created from Inner
; the first two are equivalent:
class Outer<T>
{
class Inner<U>
{
public static void F(T t, U u) {...}
}
static void F(T t) {
Outer<T>.Inner<string>.F(t, "abc"); // These two statements have
Inner<string>.F(t, "abc"); // the same effect
Outer<int>.Inner<string>.F(3, "abc"); // This type is different
Outer.Inner<string>.F(t, "abc"); // Error, Outer needs type arg
}
}
尽管编程样式不正确,但嵌套类型中的类型参数可以隐藏在外部类型中声明的成员或类型参数:Although it is bad programming style, a type parameter in a nested type can hide a member or type parameter declared in the outer type:
class Outer<T>
{
class Inner<T> // Valid, hides Outer's T
{
public T t; // Refers to Inner's T
}
}
保留的成员名称Reserved member names
为了便于基础 c # 运行时实现,对于作为属性、事件或索引器的每个源成员声明,实现必须根据成员声明的类型、名称和类型保留两个方法签名。To facilitate the underlying C# run-time implementation, for each source member declaration that is a property, event, or indexer, the implementation must reserve two method signatures based on the kind of the member declaration, its name, and its type. 如果程序声明的成员的签名与这些保留签名之一匹配,则这是编译时错误,即使基础运行时实现不使用这些保留。It is a compile-time error for a program to declare a member whose signature matches one of these reserved signatures, even if the underlying run-time implementation does not make use of these reservations.
保留名称不会引入声明,因此它们不参与成员查找。The reserved names do not introduce declarations, thus they do not participate in member lookup. 但是,声明的关联的保留方法签名确实参与继承 (继承) ,可以使用 new
修饰符 (新的修饰符) 来隐藏。However, a declaration's associated reserved method signatures do participate in inheritance (Inheritance), and can be hidden with the new
modifier (The new modifier).
保留这些名称有三个用途:The reservation of these names serves three purposes:
- 如果为,则允许基础实现使用普通标识符作为方法名称,以获取或设置对 c # 语言功能的访问权限。To allow the underlying implementation to use an ordinary identifier as a method name for get or set access to the C# language feature.
- 允许其他语言使用普通标识符作为方法名称,以获取或设置 c # 语言功能的访问权限。To allow other languages to interoperate using an ordinary identifier as a method name for get or set access to the C# language feature.
- 为了帮助确保由一个符合的编译器接受的源接受另一个,通过使保留成员名称的细节在所有 c # 实现中保持一致。To help ensure that the source accepted by one conforming compiler is accepted by another, by making the specifics of reserved member names consistent across all C# implementations.
析构函数的声明 (析构 函数) 还会导致在为 析构函数) 保留 (成员名称 时保留签名。The declaration of a destructor (Destructors) also causes a signature to be reserved (Member names reserved for destructors).
为属性保留的成员名称Member names reserved for properties
对于属性 P
(属性) 类型 T
,将保留以下签名:For a property P
(Properties) of type T
, the following signatures are reserved:
T get_P();
void set_P(T value);
即使属性是只读的或只写的,都保留两个签名。Both signatures are reserved, even if the property is read-only or write-only.
示例中In the example
using System;
class A
{
public int P {
get { return 123; }
}
}
class B: A
{
new public int get_P() {
return 456;
}
new public void set_P(int value) {
}
}
class Test
{
static void Main() {
B b = new B();
A a = b;
Console.WriteLine(a.P);
Console.WriteLine(b.P);
Console.WriteLine(b.get_P());
}
}
类 A
定义一个只读属性 P
,因此保留了和方法的签名 get_P
set_P
。a class A
defines a read-only property P
, thus reserving signatures for get_P
and set_P
methods. 类 B
派生自 A
,并隐藏这两个保留的签名。A class B
derives from A
and hides both of these reserved signatures. 该示例生成以下输出:The example produces the output:
123
123
456
为事件保留的成员名称Member names reserved for events
对于 E
委托类型的事件 (事件) T
,将保留以下签名:For an event E
(Events) of delegate type T
, the following signatures are reserved:
void add_E(T handler);
void remove_E(T handler);
为索引器保留的成员名称Member names reserved for indexers
对于使用参数列表的类型为的索引 器 (索引器) T
L
,将保留以下签名: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.
在常量声明中指定的 类型 必须是 sbyte
、 byte
、、、、、、、、、、、、、 short
ushort
int
uint
long
ulong
char
float
double
decimal
bool
string
enum_type 或 reference_type。The type specified in a constant declaration must be sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
, bool
, string
, an enum_type, or a reference_type. 每个 constant_expression 必须生成一个目标类型的值,或一个可通过隐式转换转换为目标类型的类型 (隐式 转换) 。Each constant_expression must yield a value of the target type or of a type that can be converted to the target type by an implicit conversion (Implicit conversions).
常数的 类型 必须至少具有与常量本身相同的可访问 性 (辅助功能约束) 。The type of a constant must be at least as accessible as the constant itself (Accessibility constraints).
常量的值是在表达式中使用 simple_name (简单名称) 或 member_access (成员访问) 获取的。The value of a constant is obtained in an expression using a simple_name (Simple names) or a member_access (Member access).
常数本身可以参与 constant_expression。A constant can itself participate in a constant_expression. 因此,在需要 constant_expression 的任何构造中都可以使用常量。Thus, a constant may be used in any construct that requires a constant_expression. 此类构造的示例包括 case
标签、 goto case
语句、 enum
成员声明、特性和其他常量声明。Examples of such constructs include case
labels, goto case
statements, enum
member declarations, attributes, and other constant declarations.
如 常数表达式中所述, constant_expression 是可在编译时完全计算的表达式。As described in Constant expressions, a constant_expression is an expression that can be fully evaluated at compile-time. 由于不允许使用以外的 reference_type 的非 null 值的唯一方法 string
是应用 new
运算符,因此 new
,由于不允许在 constant_expression 中使用运算符,因此除外, reference_type 的常量的唯一可能的值 string
是 null
。Since the only way to create a non-null value of a reference_type other than string
is to apply the new
operator, and since the new
operator is not permitted in a constant_expression, the only possible value for constants of reference_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
生成值 10
、 11
和 12
。the compiler first evaluates A.Y
, then evaluates B.Z
, and finally evaluates A.X
, producing the values 10
, 11
, and 12
. 常数声明可能依赖于其他程序中的常量,但这种依赖关系只能在一个方向上进行。Constant declarations may depend on constants from other programs, but such dependencies are only possible in one direction. 参考上面的示例,如果 A
B
在不同的程序中声明了和,则可能会 A.X
依赖 B.Z
但 B.Z
随后不会同时依赖于 A.Y
。Referring to the example above, if A
and B
were declared in separate programs, it would be possible for A.X
to depend on B.Z
, but B.Z
could then not simultaneously depend on A.Y
.
字段Fields
*字段 _ 是表示与对象或类关联的变量的成员。A *field _ is a member that represents a variable associated with an object or class. _Field_declaration * 介绍给定类型的一个或多个字段。A _field_declaration* introduces one or more fields of a given type.
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| field_modifier_unsafe
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
;
variable_initializer
: expression
| array_initializer
;
Field_declaration 可能包括一组 特性 (特性) 、 new
修饰符 (新修饰符) 、 (访问修饰符) 的四个访问修饰符的有效组合 (以及) 的 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 必须表示包含的类型的实例 M
。When a field is referenced in a member_access (Member access) of the form E.M
, if M
is a static field, E
must denote a type containing M
, and if M
is an instance field, E must denote an instance of a type containing M
.
静态成员和实例成员之间的差异在 静态成员和实例成员中进行了进一步讨论。The differences between static and instance members are discussed further in Static and instance members.
只读字段Readonly fields
如果 field_declaration 包含 readonly
修饰符,则声明引入的字段为 只读字段。When a field_declaration includes a readonly
modifier, the fields introduced by the declaration are readonly fields. 向 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
ref
。These are also the only contexts in which it is valid to pass areadonly
field as anout
orref
parameter.
如果尝试 readonly
在任何其他上下文中将其分配给字段或将其作为 out
或 ref
参数传递,则会发生编译时错误。Attempting to assign to a readonly
field or pass it as an out
or ref
parameter in any other context is a compile-time error.
使用常量的静态 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
Red
、 Green
和 Blue
成员不能声明为成员, const
因为它们的值不能在编译时计算。the Black
, White
, Red
, Green
, and Blue
members cannot be declared as const
members because their values cannot be computed at compile-time. 但是,将它们声明 static readonly
为的效果大致相同。However, declaring them static readonly
instead has much the same effect.
常量和静态 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);
}
}
}
Program1
和 Program2
命名空间表示单独编译的两个程序。The Program1
and Program2
namespaces denote two programs that are compiled separately. 由于 Program1.Utils.X
被声明为静态只读字段,因此语句输出的值 Console.WriteLine
在编译时是未知的,而是在运行时获取。Because Program1.Utils.X
is declared as a static readonly field, the value output by the Console.WriteLine
statement is not known at compile-time, but rather is obtained at run-time. 因此,如果的值已 X
更改,并且重新 Program1
编译,则 Console.WriteLine
即使未重新编译,该语句也会输出新值 Program2
。Thus, if the value of X
is changed and Program1
is recompiled, the Console.WriteLine
statement will output the new value even if Program2
isn't recompiled. 但是,的 X
值已是常量,则的值 X
将在编译时获得 Program2
,并且在重新编译之前,中的更改仍 Program1
不受影响 Program2
。However, had X
been a constant, the value of X
would have been obtained at the time Program2
was compiled, and would remain unaffected by changes in Program1
until Program2
is recompiled.
可变字段Volatile fields
当 field_declaration 包含修饰符时 volatile
,该声明引入的字段是 可变字段。When a field_declaration includes a volatile
modifier, the fields introduced by that declaration are volatile fields.
对于非易失性字段,重新排序指令的优化方法可能会导致意外且不可预知的结果,这会导致多线程程序访问字段而不同步,如 lock_statement 提供的 (lock 语句) 。For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock_statement (The lock statement). 这些优化可以由编译器、运行时系统或硬件执行。These optimizations can be performed by the compiler, by the run-time system, or by hardware. 对于可变字段,此类重新排序优化将受到限制:For volatile fields, such reordering optimizations are restricted:
- 读取可变字段称为 易失性读取。A read of a volatile field is called a volatile read. 可变读取具有 "获取语义";也就是说,保证在对指令序列中出现其后的内存进行引用之前发生。A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
- 可变字段的写入称为 可变写入。A write of a volatile field is called a volatile write. 可变写入具有 "发布语义";也就是说,保证在指令序列中写入指令之前的任何内存引用后发生。A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.
这些限制确保所有线程观察易失性写入操作(由任何其他线程执行)时的观察顺序与写入操作的执行顺序一致。These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed. 一致性实现不需要提供可变写入的单个总体顺序,如所有执行线程中所示。A conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution. 可变字段的类型必须是以下类型之一:The type of a volatile field must be one of the following:
- 一个 reference_type。A reference_type.
- 类型
byte
、sbyte
、short
、、、、、、、ushort
int
uint
char
float
bool
System.IntPtr
或System.UIntPtr
。The typebyte
,sbyte
,short
,ushort
,int
,uint
,char
,float
,bool
,System.IntPtr
, orSystem.UIntPtr
. - 具有枚举基类型、、、
byte
sbyte
short
ushort
、int
或uint
的 enum_type。An enum_type having an enum base type ofbyte
,sbyte
,short
,ushort
,int
, oruint
.
示例The example
using System;
using System.Threading;
class Test
{
public static int result;
public static volatile bool finished;
static void Thread2() {
result = 143;
finished = true;
}
static void Main() {
finished = false;
// Run Thread2() in a new thread
new Thread(new ThreadStart(Thread2)).Start();
// Wait for Thread2 to signal that it has a result by setting
// finished to true.
for (;;) {
if (finished) {
Console.WriteLine("result = {0}", result);
return;
}
}
}
}
生成输出:produces the output:
result = 143
在此示例中,方法将 Main
启动一个运行方法的新线程 Thread2
。In this example, the method Main
starts a new thread that runs the method Thread2
. 此方法将值存储到名为的非易失性字段 result
,然后 true
将其存储在可变字段中 finished
。This method stores a value into a non-volatile field called result
, then stores true
in the volatile field finished
. 主线程等待字段 finished
设置为 true
,然后读取字段 result
。The main thread waits for the field finished
to be set to true
, then reads the field result
. 由于已 finished
声明 volatile
,主线程必须 143
从字段中读取值 result
。Since finished
has been declared volatile
, the main thread must read the value 143
from the field result
. 如果字段 finished
尚未声明 volatile
,则允许存储区在 result
应用商店之后对主线程可见 finished
,因此,主线程将 0
从该字段中读取值 result
的情况。If the field finished
had not been declared volatile
, then it would be permissible for the store to result
to be visible to the main thread after the store to finished
, and hence for the main thread to read the value 0
from the field result
. 声明 finished
为 volatile
字段可防止任何此类不一致。Declaring finished
as a volatile
field prevents any such inconsistency.
字段初始化Field initialization
字段的初始值(无论是静态字段还是实例字段)是字段类型 (默认值) 默认值。The initial value of a field, whether it be a static field or an instance field, is the default value (Default values) of the field's type. 在发生此默认初始化之前,不可能观察字段的值,并且字段永远不会 "未初始化"。It is not possible to observe the value of a field before this default initialization has occurred, and a field is thus never "uninitialized". 示例The example
using System;
class Test
{
static bool b;
int i;
static void Main() {
Test t = new Test();
Console.WriteLine("b = {0}, i = {1}", b, t.i);
}
}
生成输出produces the output
b = False, i = 0
因为 b
和 i
都自动初始化为默认值。because b
and i
are both automatically initialized to default values.
变量初始值设定项Variable initializers
字段声明可能包括 variable_initializer。Field 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
将初始化为 1
。When the initializer for a
runs, the value of b
is zero, and so a
is initialized to 1
. 当的初始值设定项 b
运行时,的值 a
已存在 1
,因此 b
将初始化为 2
。When the initializer for b
runs, the value of a
is already 1
, and so b
is initialized to 2
.
静态字段初始化Static field initialization
类的静态字段变量初始值设定项对应于按其在类声明中出现的文本顺序执行的一系列赋值。The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. 如果) 在类中存在静态构造函数 (静态 构造函数,则在执行该静态构造函数之前,会立即执行静态字段初始值设定项。If a static constructor (Static constructors) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. 否则,在首次使用该类的静态字段之前,将在依赖于实现的时间执行静态字段初始值设定项。Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class. 示例The example
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
public static int X = Test.F("Init A");
}
class B
{
public static int Y = Test.F("Init B");
}
可能产生以下输出:might produce either the output:
Init A
Init B
1 1
或输出:or the output:
Init B
Init A
1 1
因为的 X
初始值设定项和 Y
的初始值设定项可按任意顺序出现,所以它们只会在对这些字段的引用之前发生。because the execution of X
's initializer and Y
's initializer could occur in either order; they are only constrained to occur before the references to those fields. 但在示例中:However, in the example:
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
static A() {}
public static int X = Test.F("Init A");
}
class B
{
static B() {}
public static int Y = Test.F("Init B");
}
输出必须为:the output must be:
Init B
Init A
1 1
由于静态构造函数按照 静态构造 函数中的定义执行 (的规则) 提供 B
(的静态构造函数,因此 B
) 必须在 A
其静态构造函数和字段初始值设定项之前运行的静态字段初始值设定项。because the rules for when static constructors execute (as defined in Static constructors) provide that B
's static constructor (and hence B
's static field initializers) must run before A
's static constructor and field initializers.
实例字段初始化Instance field initialization
类的实例字段变量初始值设定项对应于一个赋值序列,该序列会在该类的任何一个实例构造函数) (构造函数初始值设定 项后立即执行。The instance field variable initializers of a class correspond to a sequence of assignments that are executed immediately upon entry to any one of the instance constructors (Constructor initializers) of that class. 变量初始值设定项将按照它们在类声明中的显示顺序执行。The variable initializers are executed in the textual order in which they appear in the class declaration. 实例构造函数中对类实例创建和初始化过程进行了进一步说明。The class instance creation and initialization process is described further in Instance constructors.
实例字段的变量初始值设定项不能引用正在创建的实例。A variable initializer for an instance field cannot reference the instance being created. 因此, this
在变量初始值设定项中引用编译时错误,因为变量初始值设定项通过 simple_name 引用任何实例成员时出现编译时错误。Thus, it is a compile-time error to reference this
in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple_name. 示例中In the example
class A
{
int x = 1;
int y = x + 1; // Error, reference to instance member of this
}
的变量初始值设定项 y
会导致编译时错误,因为它引用正在创建的实例的成员。the variable initializer for y
results in a compile-time error because it references a member of the instance being created.
方法Methods
*方法 _ 是实现可由对象或类执行的计算或操作的成员。A *method _ is a member that implements a computation or action that can be performed by an object or class. 方法使用 _method_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.
- 声明最多包含以下修饰符之一:
static
、virtual
和override
。The declaration includes at most one of the following modifiers:static
,virtual
, andoverride
. - 声明最多包含以下修饰符之一:
new
和override
。The declaration includes at most one of the following modifiers:new
andoverride
. - 如果声明包含
abstract
修饰符,则声明不包括以下任何修饰符:static
、virtual
sealed
或extern
。If the declaration includes theabstract
modifier, then the declaration does not include any of the following modifiers:static
,virtual
,sealed
orextern
. - 如果声明包含
private
修饰符,则声明不包括以下任何修饰符:virtual
、override
或abstract
。If the declaration includes theprivate
modifier, then the declaration does not include any of the following modifiers:virtual
,override
, orabstract
. - 如果声明包含
sealed
修饰符,则声明还包括override
修饰符。If the declaration includes thesealed
modifier, then the declaration also includes theoverride
modifier. - 如果声明包含
partial
修饰符,则不包含以下任何修饰符:new
、public
、protected
、、、、、、internal
private
virtual
sealed
override
abstract
或extern
。If the declaration includes thepartial
modifier, then it does not include any of the following modifiers:new
,public
,protected
,internal
,private
,virtual
,sealed
,override
,abstract
, orextern
.
具有修饰符的方法 async
是一个异步函数,并遵循 异步函数中所述的规则。A method that has the async
modifier is an async function and follows the rules described in Async functions.
方法声明的 return_type 指定由方法计算并返回的值的类型。The return_type of a method declaration specifies the type of the value computed and returned by the method. 如果方法不返回值,则为 return_type void
。The return_type is void
if the method does not return a value. 如果声明包含 partial
修饰符,则返回类型必须为 void
。If the declaration includes the partial
modifier, then the return type must be void
.
Member_name 指定方法的名称。The member_name specifies the name of the method. 除非方法是显式接口成员实现 () 显式接口成员 实现, member_name 只是一个 标识符。Unless the method is an explicit interface member implementation (Explicit interface member implementations), the member_name is simply an identifier. 对于显式接口成员实现, member_name 由 interface_type 后跟 " .
" 和 标识符 组成。For an explicit interface member implementation, the member_name consists of an interface_type followed by a ".
" and an identifier.
可选 type_parameter_list 指定) (类型参数 的方法的类型参数。The optional type_parameter_list specifies the type parameters of the method (Type parameters). 如果指定了 type_parameter_list ,则该方法为 *泛型方法 _。If a type_parameter_list is specified the method is a *generic method _. 如果该方法具有 extern
修饰符,则不能指定 _type_parameter_list *。If the method has an extern
modifier, a _type_parameter_list* cannot be specified.
可选 formal_parameter_list 指定方法参数的参数 () 的 方法参数 。The optional formal_parameter_list specifies the parameters of the method (Method parameters).
可选 type_parameter_constraints_clause 指定对单个类型参数的约束 (类型参数约束) ,并且只能在同时提供 type_parameter_list 并且方法不具有修饰符时指定 override
。The 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.
对于 abstract
和 extern
方法, method_body 只包含一个分号。For abstract
and extern
methods, the method_body consists simply of a semicolon. 对于 partial
方法, method_body 可能包含分号、块体或表达式主体。For partial
methods the method_body may consist of either a semicolon, a block body or an expression body. 对于所有其他方法, method_body 为块体或表达式主体。For all other methods, the method_body is either a block body or an expression body.
如果 method_body 包含分号,则声明可能不包括 async
修饰符。If the method_body consists of a semicolon, then the declaration may not include the async
modifier.
方法的名称、类型参数列表和形参列表定义了该方法的签名 (签名和重载) 。The name, the type parameter list and the formal parameter list of a method define the signature (Signatures and overloading) of the method. 具体而言,方法的签名由其名称、类型参数的数目以及其形参的数量、修饰符和类型组成。Specifically, the signature of a method consists of its name, the number of type parameters and the number, modifiers, and types of its formal parameters. 出于这些目的,形参的类型中出现的方法的任何类型形参均由其名称标识,但其在方法的类型实参列表中的序号位置。返回类型不是方法签名的一部分,也不是类型参数或形参的名称。For these purposes, any type parameter of the method that occurs in the type of a formal parameter is identified not by its name, but by its ordinal position in the type argument list of the method.The return type is not part of a method's signature, nor are the names of the type parameters or the formal parameters.
方法的名称必须不同于同一个类中声明的所有其他非方法的名称。The name of a method must differ from the names of all other non-methods declared in the same class. 此外,方法的签名必须与在同一类中声明的所有其他方法的签名不同,同一类中声明的两个方法的签名可能不只是与 ref
和不同 out
。In addition, the signature of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref
and out
.
此方法的 type_parameter 在整个 method_declaration 的范围内,可用于在 return_type、 method_body 和 type_parameter_constraints_clause 中的整个范围内形成类型,而不是在 属性 中。The method's type_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_array。The formal parameter list consists of one or more comma-separated parameters of which only the last may be a parameter_array.
Fixed_parameter 包含一组可选的 特性 (特性) 、可选的 ref
out
或 this
修饰符、类型、标识符 和可选 default_argument。A fixed_parameter consists of an optional set of attributes (Attributes), an optional ref
, out
or this
modifier, a type, an identifier and an optional default_argument. 每个 fixed_parameter 都声明具有给定名称的给定类型的参数。Each fixed_parameter declares a parameter of the given type with the given name. this
修饰符将方法指定为扩展方法,并且只允许在静态方法的第一个参数上使用。The this
modifier designates the method as an extension method and is only allowed on the first parameter of a static method. 扩展方法中进一步介绍了扩展方法。Extension methods are further described in Extension methods.
使用 default_argument 的 fixed_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*.
ref
或 out
参数不能有 default_argument。A ref
or out
parameter cannot have a default_argument. Default_argument 中的 表达式 必须是下列其中一项:The expression in a default_argument must be one of the following:
- 一个 constant_expressiona constant_expression
- 格式为的表达式,
new S()
其中S
是值类型an expression of the formnew S()
whereS
is a value type - 格式为的表达式,
default(S)
其中S
是值类型an expression of the formdefault(S)
whereS
is a value type
表达式 必须可通过标识或可为 null 的类型转换为参数的类型。The expression must be implicitly convertible by an identity or nullable conversion to the type of the parameter.
如果在实现分部方法声明中出现可选参数 (分部方法) ,则显式接口成员实现 (显式接口成员 实现) 或在单参数索引器声明中或在单参数索引器声明中 (索引 器) 应会发出警告,因为这些成员永远不能以允许省略参数的方式调用。If optional parameters occur in an implementing partial method declaration (Partial methods) , an explicit interface member implementation (Explicit interface member implementations) or in a single-parameter indexer declaration (Indexers) the compiler should give a warning, since these members can never be invoked in a way that permits arguments to be omitted.
Parameter_array 包含一组可选的 特性 (特性) 、 params
修饰符、 array_type 和 标识符。A parameter_array consists of an optional set of attributes (Attributes), a params
modifier, an array_type, and an identifier. 参数数组声明具有给定名称的给定数组类型的单一参数。A parameter array declares a single parameter of the given array type with the given name. 参数数组的 array_type 必须是 (数组类型) 的一维数组类型。The array_type of a parameter array must be a single-dimensional array type (Array types). 在方法调用中,参数数组允许指定给定数组类型的单个自变量,或允许指定数组元素类型的零个或多个参数。In a method invocation, a parameter array permits either a single argument of the given array type to be specified, or it permits zero or more arguments of the array element type to be specified. 参数数组中进一步介绍了参数数组。Parameter arrays are described further in Parameter arrays.
Parameter_array 可能会在可选参数之后发生,但不能具有默认值,而是省略 parameter_array 的参数会导致创建一个空数组。A parameter_array may occur after an optional parameter, but cannot have a default value -- the omission of arguments for a parameter_array would instead result in the creation of an empty array.
下面的示例演示了不同类型的参数:The following example illustrates different kinds of parameters:
public void M(
ref int i,
decimal d,
bool b = false,
bool? n = false,
string s = "Hello",
object o = null,
T t = default(T),
params int[] a
) { }
在 formal_parameter_list 中 M
, i
是必需的 ref 参数, d
是必需的值参数,、 b
和是 s
o
t
可选值参数, a
是参数数组。In the formal_parameter_list for M
, i
is a required ref parameter, d
is a required value parameter, b
, s
, o
and t
are optional value parameters and a
is a parameter array.
方法声明为参数、类型参数和局部变量创建了单独的声明空间。A method declaration creates a separate declaration space for parameters, type parameters and local variables. 名称通过方法的类型形参列表和形参列表以及方法 块 中的局部变量声明引入此声明空间。Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. 方法声明空间的两个成员具有相同的名称是错误的。It is an error for two members of a method declaration space to have the same name. 如果嵌套声明空间的方法声明空间和局部变量声明空间包含具有相同名称的元素,则是错误的。It is an error for the method declaration space and the local variable declaration space of a nested declaration space to contain elements with the same name.
方法调用 (方法 调用) 创建特定于该方法的形参和局部变量的副本,并且调用的参数列表将值或变量引用分配给新创建的形参。A method invocation (Method invocations) creates a copy, specific to that invocation, of the formal parameters and local variables of the method, and the argument list of the invocation assigns values or variable references to the newly created formal parameters. 在方法的 块 中,可以通过 (简单名称) simple_name 表达式中的标识符引用形参。Within the block of a method, formal parameters can be referenced by their identifiers in simple_name expressions (Simple names).
有四种形参:There are four kinds of formal parameters:
- 值参数,在没有任何修饰符的情况下声明。Value parameters, which are declared without any modifiers.
- 引用参数,通过
ref
修饰符声明。Reference parameters, which are declared with theref
modifier. - 用修饰符声明的输出参数
out
。Output parameters, which are declared with theout
modifier. - 参数数组,用
params
修饰符声明。Parameter arrays, which are declared with theparams
modifier.
如 签名和重载中所述, ref
和 out
修饰符是方法签名的一部分,但 params
修饰符不是。As described in Signatures and overloading, the ref
and out
modifiers are part of a method's signature, but the params
modifier is not.
值参数Value parameters
使用不带修饰符声明的参数是一个值参数。A parameter declared with no modifiers is a value parameter. 值参数对应于一个局部变量,该局部变量从方法调用中提供的相应参数获取其初始值。A value parameter corresponds to a local variable that gets its initial value from the corresponding argument supplied in the method invocation.
如果形参为值形参,则方法调用中的相应实参必须是隐式转换 () 形参类型的 隐式转换 的表达式。When a formal parameter is a value parameter, the corresponding argument in a method invocation must be an expression that is implicitly convertible (Implicit conversions) to the formal parameter type.
允许方法为值参数赋值。A method is permitted to assign new values to a value parameter. 此类分配只会影响 value 参数所表示的本地存储位置,它们对方法调用中给定的实参不起作用。Such assignments only affect the local storage location represented by the value parameter—they have no effect on the actual argument given in the method invocation.
引用参数Reference parameters
使用修饰符声明的参数 ref
是一个引用参数。A parameter declared with a ref
modifier is a reference parameter. 与值参数不同,引用参数不会创建新的存储位置。Unlike a value parameter, a reference parameter does not create a new storage location. 相反,引用参数表示作为方法调用中的自变量提供的变量所在的存储位置。Instead, a reference parameter represents the same storage location as the variable given as the argument in the method invocation.
如果形参是引用参数,则方法调用中的相应参数必须由关键字 ref
后跟一个 Variable_reference (准确的规则用于确定 与形参相同的类型的明确赋值) 。When a formal parameter is a reference parameter, the corresponding argument in a method invocation must consist of the keyword ref
followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. 在将变量作为引用参数传递之前,必须对其进行明确赋值。A variable must be definitely assigned before it can be passed as a reference parameter.
在方法中,引用参数始终被视为明确赋值。Within a method, a reference parameter is always considered definitely assigned.
声明 为迭代器 (迭代 器) 的方法不能有引用参数。A method declared as an iterator (Iterators) cannot have reference parameters.
示例The example
using System;
class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("i = {0}, j = {1}", i, j);
}
}
生成输出produces the output
i = 2, j = 1
对于 Swap
在中的调用 Main
, x
表示 i
和 y
表示 j
。For the invocation of Swap
in Main
, x
represents i
and y
represents j
. 因此,调用会对和的值 i
进行交换 j
。Thus, the invocation has the effect of swapping the values of i
and j
.
在采用引用参数的方法中,多个名称可以表示相同的存储位置。In a method that takes reference parameters it is possible for multiple names to represent the same storage location. 示例中In the example
class A
{
string s;
void F(ref string a, ref string b) {
s = "One";
a = "Two";
b = "Three";
}
void G() {
F(ref s, ref s);
}
}
在中,的调用将对 F
G
和传递对 s
的引用 a
b
。the invocation of F
in G
passes a reference to s
for both a
and b
. 因此,在该调用中,名称 s
、 a
和 b
都指同一存储位置,三个分配都修改实例字段 s
。Thus, for that invocation, the names s
, a
, and b
all refer to the same storage location, and the three assignments all modify the instance field s
.
输出参数Output parameters
使用修饰符声明的参数 out
是一个输出参数。A parameter declared with an out
modifier is an output parameter. 与引用参数类似,output 参数不会创建新的存储位置。Similar to a reference parameter, an output parameter does not create a new storage location. 相反,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
请注意, dir
和 name
变量在传递到之前可以取消分配 SplitPath
,并且这些变量在调用后被视为明确赋值。Note that the dir
and name
variables can be unassigned before they are passed to SplitPath
, and that they are considered definitely assigned following the call.
参数数组Parameter arrays
使用修饰符声明的参数 params
是一个参数数组。A parameter declared with a params
modifier is a parameter array. 如果形参表包含参数数组,则它必须是列表中的最后一个形参,并且必须是一维数组类型。If a formal parameter list includes a parameter array, it must be the last parameter in the list and it must be of a single-dimensional array type. 例如,类型 string[]
和可用作 string[][]
参数数组的类型,但类型 string[,]
不能。For example, the types string[]
and string[][]
can be used as the type of a parameter array, but the type string[,]
can not. 不能将 params
修饰符与修饰符和组合在一起 ref
out
。It is not possible to combine the params
modifier with the modifiers ref
and out
.
参数数组允许在方法调用中使用以下两种方法之一指定参数:A parameter array permits arguments to be specified in one of two ways in a method invocation:
- 为参数数组提供的参数可以是可隐式转换 () 到参数数组类型的 隐式转换 的单个表达式。The argument given for a parameter array can be a single expression that is implicitly convertible (Implicit conversions) to the parameter array type. 在这种情况下,参数数组的作用与值参数完全相同。In this case, the parameter array acts precisely like a value parameter.
- 此外,调用可以为参数数组指定零个或多个参数,其中每个参数都是隐式 转换 (隐式转换) 到参数数组的元素类型的表达式。Alternatively, the invocation can specify zero or more arguments for the parameter array, where each argument is an expression that is implicitly convertible (Implicit conversions) to the element type of the parameter array. 在这种情况下,调用会创建一个参数数组类型的实例,该实例的长度与参数的数量相对应,使用给定的参数值初始化数组实例的元素,并使用新创建的数组实例作为实参。In this case, the invocation creates an instance of the parameter array type with a length corresponding to the number of arguments, initializes the elements of the array instance with the given argument values, and uses the newly created array instance as the actual argument.
除了允许在调用中使用可变数量的自变量,参数数组与值参数完全等效 (值 参数) 属于同一类型。Except for allowing a variable number of arguments in an invocation, a parameter array is precisely equivalent to a value parameter (Value parameters) of the same type.
示例The example
using System;
class Test
{
static void F(params int[] args) {
Console.Write("Array contains {0} elements:", args.Length);
foreach (int i in args)
Console.Write(" {0}", i);
Console.WriteLine();
}
static void Main() {
int[] arr = {1, 2, 3};
F(arr);
F(10, 20, 30, 40);
F();
}
}
生成输出produces the output
Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:
第一次调用 F
只是将数组 a
作为值参数进行传递。The first invocation of F
simply passes the array a
as a value parameter. 第二次调用 F
会自动创建 int[]
具有给定元素值的四元素,并将该数组实例作为值参数进行传递。The second invocation of F
automatically creates a four-element int[]
with the given element values and passes that array instance as a value parameter. 同样,第三次调用 F
会创建一个零元素 int[]
,并将该实例作为值参数进行传递。Likewise, the third invocation of F
creates a zero-element int[]
and passes that instance as a value parameter. 第二次和第三次调用完全等效于写入:The second and third invocations are precisely equivalent to writing:
F(new int[] {10, 20, 30, 40});
F(new int[] {});
在执行重载决策时,具有参数数组的方法可能以其常规格式或其展开形式 (适用的函数成员) 。When performing overload resolution, a method with a parameter array may be applicable either in its normal form or in its expanded form (Applicable function member). 仅当方法的正常形式不适用,并且仅当与展开的窗体具有相同签名的适用方法未在同一类型中声明时,方法的展开形式才可用。The expanded form of a method is available only if the normal form of the method is not applicable and only if an applicable method with the same signature as the expanded form is not already declared in the same type.
示例The example
using System;
class Test
{
static void F(params object[] a) {
Console.WriteLine("F(object[])");
}
static void F() {
Console.WriteLine("F()");
}
static void F(object a0, object a1) {
Console.WriteLine("F(object,object)");
}
static void Main() {
F();
F(1);
F(1, 2);
F(1, 2, 3);
F(1, 2, 3, 4);
}
}
生成输出produces the output
F();
F(object[]);
F(object,object);
F(object[]);
F(object[]);
在此示例中,具有参数数组的方法的两个可能的展开形式都已作为常规方法包含在类中。In the example, two of the possible expanded forms of the method with a parameter array are already included in the class as regular methods. 因此,在执行重载决策时不考虑这些扩展窗体,第一个和第三个方法调用会选择常规方法。These expanded forms are therefore not considered when performing overload resolution, and the first and third method invocations thus select the regular methods. 当类声明具有参数数组的方法时,也不太常见,还应将一些展开形式包含为常规方法。When a class declares a method with a parameter array, it is not uncommon to also include some of the expanded forms as regular methods. 这样做可以避免在调用具有参数数组的方法的扩展形式时,分配发生的数组实例。By doing so it is possible to avoid the allocation of an array instance that occurs when an expanded form of a method with a parameter array is invoked.
当参数数组的类型为时 object[]
,该方法的正常形式与单个参数的所花费形式之间可能存在歧义 object
。When the type of a parameter array is object[]
, a potential ambiguity arises between the normal form of the method and the expended form for a single object
parameter. 多义性的原因是, object[]
本身可隐式转换为类型 object
。The reason for the ambiguity is that an object[]
is itself implicitly convertible to type object
. 但这种歧义不会带来任何问题,因为它可以通过在需要时插入强制转换来解决。The ambiguity presents no problem, however, since it can be resolved by inserting a cast if needed.
示例The example
using System;
class Test
{
static void F(params object[] args) {
foreach (object o in args) {
Console.Write(o.GetType().FullName);
Console.Write(" ");
}
Console.WriteLine();
}
static void Main() {
object[] a = {1, "Hello", 123.456};
object o = a;
F(a);
F((object)a);
F(o);
F((object[])o);
}
}
生成输出produces the output
System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double
在的第一次和最后一次调用中 F
, F
是适用的,因为存在从参数类型到参数类型的隐式转换 (都是 object[]
) 类型。In the first and last invocations of F
, the normal form of F
is applicable because an implicit conversion exists from the argument type to the parameter type (both are of type object[]
). 因此,重载决策将选择正常的形式 F
,并将参数作为常规值参数进行传递。Thus, overload resolution selects the normal form of F
, and the argument is passed as a regular value parameter. 在第二次和第三次调用中,的正常形式 F
不适用,因为不存在从参数类型到参数类型的隐式转换 (类型 object
无法隐式转换为类型 object[]
) 。In the second and third invocations, the normal form of F
is not applicable because no implicit conversion exists from the argument type to the parameter type (type object
cannot be implicitly converted to type object[]
). 但是,的展开形式 F
适用,因此它通过重载决策进行选择。However, the expanded form of F
is applicable, so it is selected by overload resolution. 因此,调用会创建一个单元素 object[]
,并使用给定的参数值初始化数组的单个元素, (这本身就是对) 的引用 object[]
。As a result, a one-element object[]
is created by the invocation, and the single element of the array is initialized with the given argument value (which itself is a reference to an object[]
).
静态和实例方法Static and instance methods
当方法声明包含修饰符时 static
,该方法被称为静态方法。When a method declaration includes a static
modifier, that method is said to be a static method. 如果不 static
存在修饰符,则称该方法为实例方法。When no static
modifier is present, the method is said to be an instance method.
静态方法不在特定实例上运行,并且 this
在静态方法中引用是编译时错误。A static method does not operate on a specific instance, and it is a compile-time error to refer to this
in a static method.
实例方法对类的给定实例进行操作,可以将该实例作为 this
(此访问) 进行访问。An instance method operates on a given instance of a class, and that instance can be accessed as this
(This access).
当在窗体的 member_access (成员访问) 中引用方法时 E.M
,如果 M
为静态方法,则 E
必须表示包含的类型 M
,并且如果 M
是实例方法,则 E
必须表示包含的类型的实例 M
。When a method is referenced in a member_access (Member access) of the form E.M
, if M
is a static method, E
must denote a type containing M
, and if M
is an instance method, E
must denote an instance of a type containing M
.
静态成员和实例成员之间的差异在 静态成员和实例成员中进行了进一步讨论。The differences between static and instance members are discussed further in Static and instance members.
虚方法Virtual methods
当实例方法声明包含修饰符时 virtual
,该方法被称为虚方法。When an instance method declaration includes a virtual
modifier, that method is said to be a virtual method. 如果不 virtual
存在修饰符,则称该方法为非虚拟方法。When no virtual
modifier is present, the method is said to be a non-virtual method.
非虚方法的实现是不变的,无论是在声明方法的类的实例上调用方法,还是在派生类的实例中调用方法,实现都是相同的。The implementation of a non-virtual method is invariant: The implementation is the same whether the method is invoked on an instance of the class in which it is declared or an instance of a derived class. 相反,虚方法的实现可由派生类取代。In contrast, the implementation of a virtual method can be superseded by derived classes. 取代继承的虚方法的实现的过程称为 重 写该方法 (重写) 的方法。The process of superseding the implementation of an inherited virtual method is known as overriding that method (Override methods).
在虚方法调用中,调用所针对的实例的 *运行时类型 _ 确定要调用的实际方法实现。In a virtual method invocation, the *run-time type _ of the instance for which that invocation takes place determines the actual method implementation to invoke. 在非虚拟方法调用中,实例的 _ 编译时类型* 是确定因素。In a non-virtual method invocation, the _ compile-time type* of the instance is the determining factor. 确切地说,使用 N
编译时类型和运行时类型的实例上的参数列表调用名为的方法时 A
C
R
(其中, R
C
或派生自 C
) 的类,则按如下方式处理调用:In precise terms, when a method named N
is invoked with an argument list A
on an instance with a compile-time type C
and a run-time type R
(where R
is either C
or a class derived from C
), the invocation is processed as follows:
- 首先,将重载决策应用于
C
、N
和A
,以便从中声明的方法集中选择特定的方法,M
并由继承C
。First, overload resolution is applied toC
,N
, andA
, to select a specific methodM
from the set of methods declared in and inherited byC
. 方法调用中对此进行了说明。This is described in Method invocations. - 如果
M
是非虚方法,则M
调用。Then, ifM
is a non-virtual method,M
is invoked. - 否则,
M
为虚方法,并调用与有关的派生程度最高的M
R
。Otherwise,M
is a virtual method, and the most derived implementation ofM
with respect toR
is invoked.
对于在类中声明或继承的每个虚方法,存在与该类相关的方法的 最大派生实现 。For every virtual method declared in or inherited by a class, there exists a most derived implementation of the method with respect to that class. 与类相关的虚拟方法的派生程度最高的实现如下所示 M
R
:The most derived implementation of a virtual method M
with respect to a class R
is determined as follows:
- 如果
R
包含的引入virtual
声明M
,则这是的派生程度最高的M
。IfR
contains the introducingvirtual
declaration ofM
, then this is the most derived implementation ofM
. - 否则,如果
R
包含override
的M
,则这是的派生程度最高的M
。Otherwise, ifR
contains anoverride
ofM
, then this is the most derived implementation ofM
. - 否则,相对于的派生程度最高的实现与
M
R
相对于的直接基类的派生程度相同M
R
。Otherwise, the most derived implementation ofM
with respect toR
is the same as the most derived implementation ofM
with respect to the direct base class ofR
.
下面的示例阐释了虚方法和非虚方法之间的差异:The following example illustrates the differences between virtual and non-virtual methods:
using System;
class A
{
public void F() { Console.WriteLine("A.F"); }
public virtual void G() { Console.WriteLine("A.G"); }
}
class B: A
{
new public void F() { Console.WriteLine("B.F"); }
public override void G() { Console.WriteLine("B.G"); }
}
class Test
{
static void Main() {
B b = new B();
A a = b;
a.F();
b.F();
a.G();
b.G();
}
}
在此示例中, A
引入了一个非虚方法 F
和一个虚方法 G
。In the example, A
introduces a non-virtual method F
and a virtual method G
. 类 B
引入新的非虚方法 F
,从而隐藏继承的 F
,同时还会重写继承的方法 G
。The class B
introduces a new non-virtual method F
, thus hiding the inherited F
, and also overrides the inherited method G
. 该示例生成以下输出:The example produces the output:
A.F
B.F
B.G
B.G
请注意,该语句将 a.G()
调用 B.G
,而不是 A.G
。Notice that the statement a.G()
invokes B.G
, not A.G
. 这是因为实例的运行时类型 (B
) ,而不是) 的 (实例的编译时类型 A
,将确定要调用的实际方法实现。This is because the run-time type of the instance (which is B
), not the compile-time type of the instance (which is A
), determines the actual method implementation to invoke.
由于允许方法隐藏继承方法,因此类可能包含多个具有相同签名的虚拟方法。Because methods are allowed to hide inherited methods, it is possible for a class to contain several virtual methods with the same signature. 这并不会造成多义性问题,因为所有派生方法都是隐藏的。This does not present an ambiguity problem, since all but the most derived method are hidden. 示例中In the example
using System;
class A
{
public virtual void F() { Console.WriteLine("A.F"); }
}
class B: A
{
public override void F() { Console.WriteLine("B.F"); }
}
class C: B
{
new public virtual void F() { Console.WriteLine("C.F"); }
}
class D: C
{
public override void F() { Console.WriteLine("D.F"); }
}
class Test
{
static void Main() {
D d = new D();
A a = d;
B b = d;
C c = d;
a.F();
b.F();
c.F();
d.F();
}
}
C
和 D
类包含两个具有相同签名的虚方法:由引入的虚拟方法 A
,以及由引入的虚拟方法 C
。the C
and D
classes contain two virtual methods with the same signature: The one introduced by A
and the one introduced by C
. 引入的方法 C
隐藏从继承的方法 A
。The method introduced by C
hides the method inherited from A
. 因此,中的重写声明 D
会重写中引入的方法 C
,并且不可能 D
重写引入的方法 A
。Thus, the override declaration in D
overrides the method introduced by C
, and it is not possible for D
to override the method introduced by A
. 该示例生成以下输出:The example produces the output:
B.F
B.F
D.F
D.F
请注意,可以通过 D
不隐藏方法的派生程度更小的类型访问的实例来调用隐藏虚方法。Note that it is possible to invoke the hidden virtual method by accessing an instance of D
through a less derived type in which the method is not hidden.
重写方法Override methods
当实例方法声明包含修饰符时 override
,该方法被称为 替代方法。When an instance method declaration includes an override
modifier, the method is said to be an override method. 重写方法使用相同的签名重写继承的虚方法。An override method overrides an inherited virtual method with the same signature. 但如果虚方法声明中引入新方法,重写方法声明通过提供相应方法的新实现代码,专门针对现有的继承虚方法。Whereas a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation of that method.
由声明重写的方法 override
称为 重写基方法。The method overridden by an override
declaration is known as the overridden base method. 对于 M
在类中声明的重写方法 C
,重写的基方法是通过检查的每个基类类型确定的 C
,从的直接基类类型开始, C
然后继续每个连续的直接基类类型,直到至少有一个可访问方法与 M
类型参数替换后具有相同签名的可访问方法。For an override method M
declared in a class C
, the overridden base method is determined by examining each base class type of C
, starting with the direct base class type of C
and continuing with each successive direct base class type, until in a given base class type at least one accessible method is located which has the same signature as M
after substitution of type arguments. 为了定位重写的基方法,如果方法为(如果是,则为; 如果它是,则为; 如果它是,则为 public
protected
protected internal
,或者 internal
在与相同的程序中声明 C
的方法)。For the purposes of locating the overridden base method, a method is considered accessible if it is public
, if it is protected
, if it is protected internal
, or if it is internal
and declared in the same program as C
.
除非以下所有条件都适用于重写声明,否则将发生编译时错误:A compile-time error occurs unless all of the following are true for an override declaration:
- 重写的基方法可以按上文所述进行定位。An overridden base method can be located as described above.
- 正好有一个这种重写基方法。There is exactly one such overridden base method. 仅当基类类型为构造类型时,此限制才会生效,在这种情况下,类型参数的替换会使两个方法的签名相同。This restriction has effect only if the base class type is a constructed type where the substitution of type arguments makes the signature of two methods the same.
- 重写的基方法为虚拟、抽象或重写方法。The overridden base method is a virtual, abstract, or override method. 换言之,重写的基方法不能是静态的或非虚的。In other words, the overridden base method cannot be static or non-virtual.
- 重写的基方法不是密封的方法。The overridden base method is not a sealed method.
- 重写方法和重写的基方法具有相同的返回类型。The override method and the overridden base method have the same return type.
- 重写声明和重写的基方法具有相同的声明的可访问性。The override declaration and the overridden base method have the same declared accessibility. 换言之,重写声明不能更改虚拟方法的可访问性。In other words, an override declaration cannot change the accessibility of the virtual method. 但是,如果重写的基方法是受保护的内部方法,并且在与包含 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
中声明的方法 A
。the base.PrintFields()
invocation in B
invokes the PrintFields
method declared in A
. Base_access 禁用虚拟调用机制,只是将基方法视为非虚拟方法。A base_access disables the virtual invocation mechanism and simply treats the base method as a non-virtual method. 如果 B
已编写调用 ((A)this).PrintFields()
,它将以递归方式调用 PrintFields
中声明的方法 B
,而不是在中声明的方法, A
因为是虚拟的, PrintFields
运行时类型 ((A)this)
是 B
。Had the invocation in B
been written ((A)this).PrintFields()
, it would recursively invoke the PrintFields
method declared in B
, not the one declared in A
, since PrintFields
is virtual and the run-time type of ((A)this)
is B
.
只有通过包含 override
修饰符,方法才能重写另一个方法。Only by including an override
modifier can a method override another method. 在所有其他情况下,具有与继承方法相同的签名的方法只是隐藏了继承方法。In all other cases, a method with the same signature as an inherited method simply hides the inherited method. 示例中In the example
class A
{
public virtual void F() {}
}
class B: A
{
public virtual void F() {} // Warning, hiding inherited F()
}
F
中的方法 B
不包括 override
修饰符,因此不会重写中的 F
方法 A
。the F
method in B
does not include an override
modifier and therefore does not override the F
method in A
. 相反, F
中的方法会 B
隐藏中的方法 A
,并且会报告警告,因为声明不包含 new
修饰符。Rather, the F
method in B
hides the method in A
, and a warning is reported because the declaration does not include a new
modifier.
示例中In the example
class A
{
public virtual void F() {}
}
class B: A
{
new private void F() {} // Hides A.F within body of B
}
class C: B
{
public override void F() {} // Ok, overrides A.F
}
F
中的方法 B
隐藏 F
从继承的虚方法 A
。the F
method in B
hides the virtual F
method inherited from A
. 由于中的 F
新 B
具有私有访问权限,因此其范围仅包括的类体,不 B
扩展到 C
。Since the new F
in B
has private access, its scope only includes the class body of B
and does not extend to C
. 因此,中的声明 F
C
允许重写 F
从继承的 A
。Therefore, the declaration of F
in C
is permitted to override the F
inherited from A
.
密封方法Sealed methods
当实例方法声明包含修饰符时 sealed
,该方法被称为 密封方法。When an instance method declaration includes a sealed
modifier, that method is said to be a sealed method. 如果实例方法声明包含 sealed
修饰符,则它还必须包含 override
修饰符。If an instance method declaration includes the sealed
modifier, it must also include the override
modifier. 使用 sealed
修饰符可防止派生类进一步重写方法。Use of the sealed
modifier prevents a derived class from further overriding the method.
示例中In the example
using System;
class A
{
public virtual void F() {
Console.WriteLine("A.F");
}
public virtual void G() {
Console.WriteLine("A.G");
}
}
class B: A
{
sealed override public void F() {
Console.WriteLine("B.F");
}
override public void G() {
Console.WriteLine("B.G");
}
}
class C: B
{
override public void G() {
Console.WriteLine("C.G");
}
}
类 B
提供两个重写方法: F
具有修饰符的方法和不具有的 sealed
G
方法。the class B
provides two override methods: an F
method that has the sealed
modifier and a G
method that does not. B
使用密封 modifier
会阻止 C
进一步重写 F
。B
's use of the sealed modifier
prevents C
from further overriding F
.
抽象方法Abstract methods
当实例方法声明包含修饰符时 abstract
,该方法被称为 抽象方法。When an instance method declaration includes an abstract
modifier, that method is said to be an abstract method. 尽管抽象方法还隐式成为虚方法,但它不能具有修饰符 virtual
。Although an abstract method is implicitly also a virtual method, it cannot have the modifier virtual
.
抽象方法声明引入新的虚方法,但不提供该方法的实现。An abstract method declaration introduces a new virtual method but does not provide an implementation of that method. 相反,非抽象派生类需要通过重写方法来提供自己的实现。Instead, non-abstract derived classes are required to provide their own implementation by overriding that method. 因为抽象方法不提供实际的实现,所以抽象方法的 method_body 只包含一个分号。Because an abstract method provides no actual implementation, the method_body of an abstract method simply consists of a semicolon.
只有抽象类 (抽象 类) 才能使用抽象方法声明。Abstract method declarations are only permitted in abstract classes (Abstract classes).
示例中In the example
public abstract class Shape
{
public abstract void Paint(Graphics g, Rectangle r);
}
public class Ellipse: Shape
{
public override void Paint(Graphics g, Rectangle r) {
g.DrawEllipse(r);
}
}
public class Box: Shape
{
public override void Paint(Graphics g, Rectangle r) {
g.DrawRect(r);
}
}
Shape
类定义可自行绘制的几何形状对象的抽象概念。the Shape
class defines the abstract notion of a geometrical shape object that can paint itself. Paint
方法是抽象的,因为没有有意义的默认实现。The Paint
method is abstract because there is no meaningful default implementation. Ellipse
和 Box
类是具体的 Shape
实现。The Ellipse
and Box
classes are concrete Shape
implementations. 由于这些类是非抽象类,因此它们需要重写 Paint
方法并提供实际实现。Because these classes are non-abstract, they are required to override the Paint
method and provide an actual implementation.
如果 base_access (基本访问) 引用抽象方法,则会发生编译时错误。It is a compile-time error for a base_access (Base access) to reference an abstract method. 示例中In the example
abstract class A
{
public abstract void F();
}
class B: A
{
public override void F() {
base.F(); // Error, base.F is abstract
}
}
将报告调用的编译时错误, base.F()
因为它引用了抽象方法。a compile-time error is reported for the base.F()
invocation because it references an abstract method.
允许抽象方法声明重写虚拟方法。An abstract method declaration is permitted to override a virtual method. 这允许抽象类强制在派生类中重新实现方法,并使方法的原始实现不可用。This allows an abstract class to force re-implementation of the method in derived classes, and makes the original implementation of the method unavailable. 示例中In the example
using System;
class A
{
public virtual void F() {
Console.WriteLine("A.F");
}
}
abstract class B: A
{
public abstract override void F();
}
class C: B
{
public override void F() {
Console.WriteLine("C.F");
}
}
类 A
声明一个虚方法,类 B
使用抽象方法重写此方法,类 C
将重写抽象方法以提供其自己的实现。class A
declares a virtual method, class B
overrides this method with an abstract method, and class C
overrides the abstract method to provide its own implementation.
外部方法External methods
当方法声明包含修饰符时 extern
,该方法被称为 *外部方法 _。When a method declaration includes an extern
modifier, that method is said to be an *external method _. 外部方法在外部实现,通常使用 c # 以外的语言。External methods are implemented externally, typically using a language other than C#. 由于外部方法声明不提供实际的实现,因此外部方法的 _method_body * 只包含一个分号。Because an external method declaration provides no actual implementation, the _method_body* of an external method simply consists of a semicolon. 外部方法不能是泛型的。An external method may not be generic.
extern
修饰符通常与 DllImport
(互操作) COM 和 Win32 组件互操作的特性结合使用,从而允许 Dll (动态链接库) 实现外部方法。The extern
modifier is typically used in conjunction with a DllImport
attribute (Interoperation with COM and Win32 components), allowing external methods to be implemented by DLLs (Dynamic Link Libraries). 执行环境可能支持可提供外部方法实现的其他机制。The execution environment may support other mechanisms whereby implementations of external methods can be provided.
当外部方法包含特性时 DllImport
,该方法声明还必须包含 static
修饰符。When an external method includes a DllImport
attribute, the method declaration must also include a static
modifier. 此示例演示如何使用 extern
修饰符和 DllImport
特性:This example demonstrates the use of the extern
modifier and the DllImport
attribute:
using System.Text;
using System.Security.Permissions;
using System.Runtime.InteropServices;
class Path
{
[DllImport("kernel32", SetLastError=true)]
static extern bool CreateDirectory(string name, SecurityAttribute sa);
[DllImport("kernel32", SetLastError=true)]
static extern bool RemoveDirectory(string name);
[DllImport("kernel32", SetLastError=true)]
static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);
[DllImport("kernel32", SetLastError=true)]
static extern bool SetCurrentDirectory(string name);
}
(概述的分部方法) Partial methods (recap)
当方法声明包含修饰符时 partial
,该方法被称为 分部方法。When a method declaration includes a partial
modifier, that method is said to be a partial method. 分部方法只能声明为分部类型 () 部分类型 的成员,并且受到多种限制。Partial methods can only be declared as members of partial types (Partial types), and are subject to a number of restrictions. 分部方法中进一步介绍了分部方法。Partial methods are further described in Partial methods.
扩展方法Extension methods
当方法的第一个参数包含修饰符时 this
,该方法被称为 扩展方法。When the first parameter of a method includes the this
modifier, that method is said to be an extension method. 扩展方法只能在非泛型非嵌套静态类中声明。Extension methods can only be declared in non-generic, non-nested static classes. 扩展方法的第一个参数不能包含除以外的任何修饰符 this
,并且参数类型不能是指针类型。The first parameter of an extension method can have no modifiers other than this
, and the parameter type cannot be a pointer type.
下面是一个声明两个扩展方法的静态类示例:The following is an example of a static class that declares two extension methods:
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length - index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
扩展方法是一个常规静态方法。An extension method is a regular static method. 此外,在其封闭静态类处于范围内时,可以使用实例方法调用语法调用扩展方法 (扩展方法 调用语法) ,使用接收方表达式作为第一个参数。In addition, where its enclosing static class is in scope, an extension method can be invoked using instance method invocation syntax (Extension method invocations), using the receiver expression as the first argument.
下面的程序使用上面声明的扩展方法:The following program uses the extension methods declared above:
static class Program
{
static void Main() {
string[] strings = { "1", "22", "333", "4444" };
foreach (string s in strings.Slice(1, 2)) {
Console.WriteLine(s.ToInt32());
}
}
}
Slice
方法在上可用 string[]
,并且 ToInt32
方法可在上使用 string
,因为它们已被声明为扩展方法。The Slice
method is available on the string[]
, and the ToInt32
method is available on string
, because they have been declared as extension methods. 使用普通的静态方法调用,程序的含义与下面相同:The meaning of the program is the same as the following, using ordinary static method calls:
static class Program
{
static void Main() {
string[] strings = { "1", "22", "333", "4444" };
foreach (string s in Extensions.Slice(strings, 1, 2)) {
Console.WriteLine(Extensions.ToInt32(s));
}
}
}
方法主体Method body
方法声明的 method_body 包含一个块体、一个表达式体或一个分号。The method_body of a method declaration consists of either a block body, an expression body or a semicolon.
void
如果返回类型为 void
,或者该方法为 async 并且返回类型为,则为方法的结果类型 System.Threading.Tasks.Task
。The result type of a method is void
if the return type is void
, or if the method is async and the return type is System.Threading.Tasks.Task
. 否则,非异步方法的结果类型是其返回类型,并且具有返回类型的异步方法的结果类型 System.Threading.Tasks.Task<T>
是 T
。Otherwise, the result type of a non-async method is its return type, and the result type of an async method with return type System.Threading.Tasks.Task<T>
is T
.
当方法具有 void
结果类型和块体时, return
不允许在块中 (返回语句) 的语句指定表达式。When a method has a void
result type and a block body, return
statements (The return statement) in the block are not permitted to specify an expression. 如果 void 方法的执行块正常完成 (即,控制流从方法体的末尾) ,则该方法只返回到当前调用方。If execution of the block of a void method completes normally (that is, control flows off the end of the method body), that method simply returns to its current caller.
如果方法具有 void
结果和表达式体,则表达式 E
必须为 statement_expression,且正文完全等效于窗体的块体 { E; }
。When a method has a void
result and an expression body, the expression E
must be a statement_expression, and the body is exactly equivalent to a block body of the form { E; }
.
当方法具有非 void 结果类型和块体时, return
块中的每个语句都必须指定一个可隐式转换为结果类型的表达式。When a method has a non-void result type and a block body, each return
statement in the block must specify an expression that is implicitly convertible to the result type. 返回值的方法的块体的终结点必须是无法访问的。The endpoint of a block body of a value-returning method must not be reachable. 换言之,在带有块体的值返回方法中,不允许控件流过方法体的末尾。In other words, in a value-returning method with a block body, control is not permitted to flow off the end of the method body.
如果方法具有非 void 结果类型和表达式体,则表达式必须可隐式转换为结果类型,并且正文与窗体的块体完全等效 { return E; }
。When a method has a non-void result type and an expression body, the expression must be implicitly convertible to the result type, and the body is exactly equivalent to a block body of the form { return E; }
.
示例中In the example
class A
{
public int F() {} // Error, return value required
public int G() {
return 1;
}
public int H(bool b) {
if (b) {
return 1;
}
else {
return 0;
}
}
public int I(bool b) => b ? 1 : 0;
}
返回值 F
方法会导致编译时错误,因为控件可以流过方法体的末尾。the value-returning F
method results in a compile-time error because control can flow off the end of the method body. G
和 H
方法是正确的,因为所有可能的执行路径都以指定返回值的 return 语句结尾。The G
and H
methods are correct because all possible execution paths end in a return statement that specifies a return value. I
方法是正确的,因为它的主体等效于其中只包含一个返回语句的语句块。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_name 由 interface_type 后跟 " .
" 和 标识符 组成。For an explicit interface member implementation (Explicit interface member implementations), the member_name consists of an interface_type followed by a ".
" and an identifier.
属性的 类型 必须至少具有与属性本身相同的可访问 性 (辅助功能约束) 。The type of a property must be at least as accessible as the property itself (Accessibility constraints).
Property_body 可以是由 访问器体 _ 或 _表达式体*_ 组成的。A property_body may either consist of an accessor body _ or an _expression body*_. 在访问器主体中,_accessor_declarations * (必须括在 " {
" 和 " }
" 标记中)声明属性的访问器 (访问 器) 。In an accessor body, _accessor_declarations*, which must be enclosed in "{
" and "}
" tokens, declare the accessors (Accessors) of the property. 访问器指定与读取和写入属性相关联的可执行语句。The accessors specify the executable statements associated with reading and writing the property.
=>
后跟表达式和分号的表达式体 E
完全等效于语句体 { get { return E; } }
,因此只能用于指定仅限 getter 的属性,其中,getter 的结果由单个表达式提供。An expression body consisting of =>
followed by an expression E
and a semicolon is exactly equivalent to the statement body { get { return E; } }
, and can therefore only be used to specify getter-only properties where the result of the getter is given by a single expression.
只能为自动实现的属性指定 property_initializer (自动实现属性) ,并使此类属性的基础字段的初始化值与 表达式 给定的值相同。A property_initializer may only be given for an automatically implemented property (Automatically implemented properties), and causes the initialization of the underlying field of such properties with the value given by the expression.
尽管用于访问属性的语法与字段的语法相同,但属性未分类为变量。Even though the syntax for accessing a property is the same as that for a field, a property is not classified as a variable. 因此,不能将属性作为 ref
或 out
参数传递。Thus, it is not possible to pass a property as a ref
or out
argument.
如果属性声明包含 extern
修饰符,则将属性称为 *外部属性 _。When a property declaration includes an extern
modifier, the property is said to be an *external property _. 由于外部属性声明不提供实际的实现,因此它的每个 _accessor_declarations * 都包含一个分号。Because an external property declaration provides no actual implementation, each of its _accessor_declarations* consists of a semicolon.
静态属性和实例属性Static and instance properties
当属性声明包含修饰符时 static
,属性被称为 *静态属性 _。When a property declaration includes a static
modifier, the property is said to be a *static property _. 如果不 static
存在修饰符,则将属性称为 _ 实例属性 *。When no static
modifier is present, the property is said to be an _*instance property**.
静态属性不与特定实例相关联,并且是 this
在静态属性的访问器中引用的编译时错误。A static property is not associated with a specific instance, and it is a compile-time error to refer to this
in the accessors of a static property.
实例属性与类的给定实例相关联,并且可以在该属性的访问 this
器中 (此访问) 访问该实例。An instance property is associated with a given instance of a class, and that instance can be accessed as this
(This access) in the accessors of that property.
当在窗体的 member_access (成员访问) 中引用属性时 E.M
,如果 M
是静态属性,则 E
必须表示包含的类型, M
如果 M
是实例属性,则 E 必须表示包含的类型的实例 M
。When a property is referenced in a member_access (Member access) of the form E.M
, if M
is a static property, E
must denote a type containing M
, and if M
is an instance property, E must denote an instance of a type containing M
.
静态成员和实例成员之间的差异在 静态成员和实例成员中进行了进一步讨论。The differences between static and instance members are discussed further in Static and instance members.
访问器Accessors
属性的 accessor_declarations 指定与读取和写入属性相关联的可执行语句。The accessor_declarations of a property specify the executable statements associated with reading and writing that property.
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
;
get_accessor_declaration
: attributes? accessor_modifier? 'get' accessor_body
;
set_accessor_declaration
: attributes? accessor_modifier? 'set' accessor_body
;
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
;
accessor_body
: block
| ';'
;
访问器声明由 get_accessor_declaration 和/或 set_accessor_declaration 组成。The accessor declarations consist of a get_accessor_declaration, a set_accessor_declaration, or both. 每个访问器声明都包含标记, get
set
后跟可选的 accessor_modifier 和 accessor_body。Each accessor declaration consists of the token get
or set
followed by an optional accessor_modifier and an accessor_body.
Accessor_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_modifier ,get
set
只允许在其中一种访问器上使用。For a property or indexer that has nooverride
modifier, an accessor_modifier is permitted only if the property or indexer has both aget
andset
accessor, and then is permitted only on one of those accessors. - 对于包含修饰符的属性或索引器
override
,访问器必须与要重写的访问器的 accessor_modifier(如果有)匹配。For a property or indexer that includes anoverride
modifier, an accessor must match the accessor_modifier, if any, of the accessor being overridden. - Accessor_modifier 必须声明一个可访问性,严格比属性或索引器本身的声明的可访问性更严格。The accessor_modifier must declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. 精确:To be precise:
- 如果属性或索引器具有的声明的可访问性
public
,则 accessor_modifier 可以是protected internal
、、internal
protected
或private
。If the property or indexer has a declared accessibility ofpublic
, the accessor_modifier may be eitherprotected internal
,internal
,protected
, orprivate
. - 如果属性或索引器具有的声明的可访问性
protected internal
,则 accessor_modifier 可以internal
是、protected
或private
。If the property or indexer has a declared accessibility ofprotected internal
, the accessor_modifier may be eitherinternal
,protected
, orprivate
. - 如果属性或索引器的可访问性声明为
internal
或protected
,则 accessor_modifier 必须是private
。If the property or indexer has a declared accessibility ofinternal
orprotected
, the accessor_modifier must beprivate
. - 如果属性或索引器的可访问性为
private
,则不能使用 accessor_modifier 。If the property or indexer has a declared accessibility ofprivate
, no accessor_modifier may be used.
- 如果属性或索引器具有的声明的可访问性
对于 abstract
和 extern
属性,每个指定访问器的 accessor_body 只是一个分号。For abstract
and extern
properties, the accessor_body for each accessor specified is simply a semicolon. 非抽象的非 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
始终命名为 value
。The implicit parameter of a set
accessor is always named value
. 如果将属性作为赋值运算符的目标 (赋值运算符) ,或者作为 ++
或 --
(后缀增量和减量运算符、 前缀增量和减量运算符) ,则 set
使用参数调用访问器,该参数的值为 (++
--
简单赋值) 提供新值的自变量的值,其值为或运算符 (的操作数。When a property is referenced as the target of an assignment (Assignment operators), or as the operand of ++
or --
(Postfix increment and decrement operators, Prefix increment and decrement operators), the set
accessor is invoked with an argument (whose value is that of the right-hand side of the assignment or the operand of the ++
or --
operator) that provides the new value (Simple assignment). 访问器的主体 set
必须符合 void
方法体中所述的方法规则。The body of a set
accessor must conform to the rules for void
methods described in Method body. 特别是, return
set
不允许访问器主体中的语句指定表达式。In particular, return
statements in the set
accessor body are not permitted to specify an expression. 由于 set
访问器隐式包含一个名为的参数 value
,因此访问器中的局部变量或常量声明会出现编译时错误 set
。Since a set
accessor implicitly has a parameter named value
, it is a compile-time error for a local variable or constant declaration in a set
accessor to have that name.
根据是否存在 get
和 set
访问器,属性按如下方式分类:Based on the presence or absence of the get
and set
accessors, a property is classified as follows:
- 同时包含
get
访问器和访问器的属性set
称为 读写 属性。A property that includes both aget
accessor and aset
accessor is said to be a read-write property. - 只有
get
访问器的属性称为 只读 属性。A property that has only aget
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 aset
accessor is said to be a write-only property. 除非作为赋值的目标,否则在表达式中引用只写属性是编译时错误。Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression.
示例中In the example
public class Button: Control
{
private string caption;
public string Caption {
get {
return caption;
}
set {
if (caption != value) {
caption = value;
Repaint();
}
}
}
public override void Paint(Graphics g, Rectangle r) {
// Painting code goes here
}
}
Button
控件声明一个公共 Caption
属性。the Button
control declares a public Caption
property. get
属性的访问器 Caption
返回存储在私有字段中的字符串 caption
。The get
accessor of the Caption
property returns the string stored in the private caption
field. set
访问器检查新值是否不同于当前值,如果是,则存储新值并重新绘制控件。The set
accessor checks if the new value is different from the current value, and if so, it stores the new value and repaints the control. 属性通常遵循上面所示的模式: get
访问器只返回一个存储在私有字段中的值,而 set
访问器则修改该私有字段,然后执行完全更新对象状态所需的任何其他操作。Properties often follow the pattern shown above: The get
accessor simply returns a value stored in a private field, and the set
accessor modifies that private field and then performs any additional actions required to fully update the state of the object.
根据 Button
上面的类,以下是属性使用的示例 Caption
:Given the Button
class above, the following is an example of use of the Caption
property:
Button okButton = new Button();
okButton.Caption = "OK"; // Invokes set accessor
string s = okButton.Caption; // Invokes get accessor
此处, set
通过将值分配给属性来调用访问器,并 get
通过引用表达式中的属性调用访问器。Here, the set
accessor is invoked by assigning a value to the property, and the get
accessor is invoked by referencing the property in an expression.
get
属性的和 set
访问器不是不同的成员,并且不能单独声明属性的访问器。The get
and set
accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. 因此,读写属性的两个访问器不可能具有不同的可访问性。As such, it is not possible for the two accessors of a read-write property to have different accessibility. 示例The example
class A
{
private string name;
public string Name { // Error, duplicate member name
get { return name; }
}
public string Name { // Error, duplicate member name
set { name = value; }
}
}
不声明单个读写属性。does not declare a single read-write property. 相反,它声明了两个具有相同名称的属性,一个为只读,一个只写。Rather, it declares two properties with the same name, one read-only and one write-only. 由于在同一类中声明的两个成员不能具有相同的名称,因此该示例将导致发生编译时错误。Since two members declared in the same class cannot have the same name, the example causes a compile-time error to occur.
当派生类用与继承属性相同的名称声明属性时,派生属性将隐藏与读取和写入相关的继承属性。When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing. 示例中In the example
class A
{
public int P {
set {...}
}
}
class B: A
{
new public int P {
get {...}
}
}
中的 P
属性在 B
中隐藏属性, P
A
与读取和写入有关。the P
property in B
hides the P
property in A
with respect to both reading and writing. 因此,在语句中Thus, in the statements
B b = new B();
b.P = 1; // Error, B.P is read-only
((A)b).P = 1; // Ok, reference to A.P
的赋值 b.P
导致报告编译时错误,因为中的只读 P
属性 B
隐藏了中的只写 P
属性 A
。the assignment to b.P
causes a compile-time error to be reported, since the read-only P
property in B
hides the write-only P
property in A
. 但请注意,强制转换可用于访问隐藏 P
属性。Note, however, that a cast can be used to access the hidden P
property.
与公共字段不同,属性可在对象的内部状态和其公共接口之间提供分隔。Unlike public fields, properties provide a separation between an object's internal state and its public interface. 请看下例:Consider the example:
class Label
{
private int x, y;
private string caption;
public Label(int x, int y, string caption) {
this.x = x;
this.y = y;
this.caption = caption;
}
public int X {
get { return x; }
}
public int Y {
get { return y; }
}
public Point Location {
get { return new Point(x, y); }
}
public string Caption {
get { return caption; }
}
}
此处, Label
类使用两个 int
字段( x
和 y
)来存储其位置。Here, the Label
class uses two int
fields, x
and y
, to store its location. 该位置作为 X
和 Y
属性以及 Location
类型的属性公开公开 Point
。The location is publicly exposed both as an X
and a Y
property and as a Location
property of type Point
. 如果在的未来版本中 Label
,在内部将位置存储为更方便的方式 Point
,则可以进行更改,而不会影响类的公共接口:If, in a future version of Label
, it becomes more convenient to store the location as a Point
internally, the change can be made without affecting the public interface of the class:
class Label
{
private Point location;
private string caption;
public Label(int x, int y, string caption) {
this.location = new Point(x, y);
this.caption = caption;
}
public int X {
get { return location.x; }
}
public int Y {
get { return location.y; }
}
public Point Location {
get { return location; }
}
public string Caption {
get { return caption; }
}
}
具有 x
和 y
而不是 public readonly
字段,则无法对类进行此类更改 Label
。Had x
and y
instead been public readonly
fields, it would have been impossible to make such a change to the Label
class.
通过属性公开状态不一定比直接公开字段的效率低。Exposing state through properties is not necessarily any less efficient than exposing fields directly. 特别是,当某个属性是非虚拟的并且只包含少量的代码时,执行环境可能会将对访问器的调用替换为访问器的实际代码。In particular, when a property is non-virtual and contains only a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. 此过程称为 内联,它使属性访问与字段访问一样高效,同时还保留了属性的灵活性。This process is known as inlining, and it makes property access as efficient as field access, yet preserves the increased flexibility of properties.
由于调用 get
访问器在概念上等效于读取字段的值,因此,访问器被视为错误的编程样式,使 get
访问器具有可观察到的副作用。Since invoking a get
accessor is conceptually equivalent to reading the value of a field, it is considered bad programming style for get
accessors to have observable side-effects. 示例中In the example
class Counter
{
private int next;
public int Next {
get { return next++; }
}
}
属性的值 Next
取决于先前访问属性的次数。the value of the Next
property depends on the number of times the property has previously been accessed. 因此,访问属性会产生一个可观察到的副作用,而属性应作为方法实现。Thus, accessing the property produces an observable side-effect, and the property should be implemented as a method instead.
访问器的 "无副作用" 约定 get
并不意味着 get
应始终编写访问器来只返回存储在字段中的值。The "no side-effects" convention for get
accessors doesn't mean that get
accessors should always be written to simply return values stored in fields. 实际上, get
访问器通常通过访问多个字段或调用方法来计算属性的值。Indeed, get
accessors often compute the value of a property by accessing multiple fields or invoking methods. 但是,正确设计的 get
访问器不会执行任何操作,从而导致对象的状态发生变化。However, a properly designed get
accessor performs no actions that cause observable changes in the state of the object.
在第一次引用资源之前,可以使用属性来延迟资源的初始化。Properties can be used to delay initialization of a resource until the moment it is first referenced. 例如:For example:
using System.IO;
public class Console
{
private static TextReader reader;
private static TextWriter writer;
private static TextWriter error;
public static TextReader In {
get {
if (reader == null) {
reader = new StreamReader(Console.OpenStandardInput());
}
return reader;
}
}
public static TextWriter Out {
get {
if (writer == null) {
writer = new StreamWriter(Console.OpenStandardOutput());
}
return writer;
}
}
public static TextWriter Error {
get {
if (error == null) {
error = new StreamWriter(Console.OpenStandardError());
}
return error;
}
}
}
Console
类包含三个属性: In
、 Out
和 Error
,分别表示标准输入、输出和错误设备。The Console
class contains three properties, In
, Out
, and Error
, that represent the standard input, output, and error devices, respectively. 通过将这些成员作为属性公开, Console
类可以延迟其初始化,直到实际使用它们。By exposing these members as properties, the Console
class can delay their initialization until they are actually used. 例如,在第一次引用 Out
属性时,如下所示For example, upon first referencing the Out
property, as in
Console.Out.WriteLine("hello, world");
为 TextWriter
输出设备创建基础。the underlying TextWriter
for the output device is created. 但如果应用程序不引用 In
和 Error
属性,则不会为这些设备创建任何对象。But if the application makes no reference to the In
and Error
properties, then no objects are created for those devices.
自动实现的属性Automatically implemented properties
对于 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), theget
accessor must exist and be accessible. - 如果使用是简单分配) (简单赋值 的目标,则
set
访问器必须存在并且可访问。If the usage is as the target of a simple assignment (Simple assignment), theset
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 theget
accessors and theset
accessor must exist and be accessible.
在下面的示例中,属性 A.Text
被属性隐藏 B.Text
,即使是在仅 set
调用访问器的上下文中。In the following example, the property A.Text
is hidden by the property B.Text
, even in contexts where only the set
accessor is called. 相反, B.Count
类无法访问属性 M
,因此改用可访问的属性 A.Count
。In contrast, the property B.Count
is not accessible to class M
, so the accessible property A.Count
is used instead.
class A
{
public string Text {
get { return "hello"; }
set { }
}
public int Count {
get { return 5; }
set { }
}
}
class B: A
{
private string text = "goodbye";
private int count = 0;
new public string Text {
get { return text; }
protected set { text = value; }
}
new protected int Count {
get { return count; }
set { count = value; }
}
}
class M
{
static void Main() {
B b = new B();
b.Count = 12; // Calls A.Count set accessor
int i = b.Count; // Calls A.Count get accessor
b.Text = "howdy"; // Error, B.Text set accessor not accessible
string s = b.Text; // Calls B.Text get accessor
}
}
用于实现接口的访问器可能没有 accessor_modifier。An accessor that is used to implement an interface may not have an accessor_modifier. 如果只使用一个访问器来实现接口,则可以使用 accessor_modifier 声明另一个访问器:If only one accessor is used to implement an interface, the other accessor may be declared with an accessor_modifier:
public interface I
{
string Prop { get; }
}
public class C: I
{
public string Prop {
get { return "April"; } // Must not have a modifier here
internal set {...} // Ok, because I.Prop has no set accessor
}
}
虚拟、密封、重写和抽象属性访问器Virtual, sealed, override, and abstract property accessors
virtual
属性声明指定属性的访问器是虚拟的。A virtual
property declaration specifies that the accessors of the property are virtual. virtual
修饰符适用于读写属性的这两个访问器,即读写属性的一个取值函数是虚拟的。The virtual
modifier applies to both accessors of a read-write property—it is not possible for only one accessor of a read-write property to be virtual.
abstract
属性声明指定属性的访问器是虚拟的,但是不提供访问器的实际实现。An abstract
property declaration specifies that the accessors of the property are virtual, but does not provide an actual implementation of the accessors. 相反,非抽象派生类需要通过重写属性来为访问器提供自己的实现。Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the property. 因为抽象属性声明的访问器不提供实际的实现,所以其 accessor_body 只包含一个分号。Because an accessor for an abstract property declaration provides no actual implementation, its accessor_body simply consists of a semicolon.
同时包含和修饰符的属性声明 abstract
override
指定属性是抽象的并且会重写基属性。A property declaration that includes both the abstract
and override
modifiers specifies that the property is abstract and overrides a base property. 此类属性的访问器也是抽象的。The accessors of such a property are also abstract.
只有抽象类 (抽象 类) 才能使用抽象属性声明。在派生类中,可以通过包含指定指令的属性声明,在派生类中重写继承的虚拟属性的访问器 override
。Abstract property declarations are only permitted in abstract classes (Abstract classes).The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an override
directive. 这称为 " 重写" 属性声明。This is known as an overriding property declaration. 重写属性声明未声明新的属性。An overriding property declaration does not declare a new property. 相反,它只是专用化现有虚拟属性的访问器的实现。Instead, it simply specializes the implementations of the accessors of an existing virtual property.
重写的属性声明必须将完全相同的可访问性修饰符、类型和名称指定为继承的属性。An overriding property declaration must specify the exact same accessibility modifiers, type, and name as the inherited property. 如果继承的属性只有一个访问器 (也就是说,如果继承的属性为只读或只写) ,则重写属性必须仅包括该访问器。If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property must include only that accessor. 如果继承的属性包含两个访问器 (也就是说,如果继承的属性是读写) ,则重写属性可以包含单个访问器或两个访问器。If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.
重写的属性声明可以包含 sealed
修饰符。An overriding property declaration may include the sealed
modifier. 使用此修饰符可防止派生类进一步重写属性。Use of this modifier prevents a derived class from further overriding the property. 密封属性的访问器也是密封的。The accessors of a sealed property are also sealed.
除了声明和调用语法中的差异外,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
访问器对应于带有属性类型返回值的无参数方法和包含属性的修饰符。Aget
accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property.set
访问器对应于一个方法,该方法具有属性类型的单个值参数、void
返回类型以及与包含属性相同的修饰符。Aset
accessor corresponds to a method with a single value parameter of the property type, avoid
return type, and the same modifiers as the containing property.
示例中In the example
abstract class A
{
int y;
public virtual int X {
get { return 0; }
}
public virtual int Y {
get { return y; }
set { y = value; }
}
public abstract int Z { get; set; }
}
X
是虚拟的只读属性, Y
是虚拟读写属性, Z
是抽象读写属性。X
is a virtual read-only property, Y
is a virtual read-write property, and Z
is an abstract read-write property. 由于 Z
是抽象的,因此 A
还必须将包含类声明为抽象类。Because Z
is abstract, the containing class A
must also be declared abstract.
派生自的类 A
如下所示:A class that derives from A
is show below:
class B: A
{
int z;
public override int X {
get { return base.X + 1; }
}
public override int Y {
set { base.Y = value < 0? 0: value; }
}
public override int Z {
get { return z; }
set { z = value; }
}
}
此处,、和的 X
声明 Y
Z
会重写属性声明。Here, the declarations of X
, Y
, and Z
are overriding property declarations. 每个属性声明都与相应的继承属性的可访问性修饰符、类型和名称完全匹配。Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. 的 get
访问器 X
和的访问 set
器 Y
使用 base
关键字访问继承的访问器。The get
accessor of X
and the set
accessor of Y
use the base
keyword to access the inherited accessors. 的声明 Z
会重写抽象访问器,因此,中没有未完成的抽象函数成员 B
,并且 B
允许成为非抽象类。The declaration of Z
overrides both abstract accessors—thus, there are no outstanding abstract function members in B
, and B
is permitted to be a non-abstract class.
当某个属性声明为时 override
,重写的代码必须能够访问所有重写的访问器。When a property is declared as an override
, any overridden accessors must be accessible to the overriding code. 此外,属性或索引器本身以及访问器的声明的可访问性必须与重写的成员和访问器的可访问性匹配。In addition, the declared accessibility of both the property or indexer itself, and of the accessors, must match that of the overridden member and accessors. 例如:For example:
public class B
{
public virtual int P {
protected set {...}
get {...}
}
}
public class D: B
{
public override int P {
protected set {...} // Must specify protected here
get {...} // Must not have a modifier here
}
}
事件Events
*事件 _ 是一个允许对象或类提供通知的成员。An *event _ is a member that enables an object or class to provide notifications. 客户端可以通过提供 _ *事件处理程序 来附加事件的可执行代码。Clients can attach executable code for events by supplying _*event handlers**.
使用 event_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_declarations。An 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.
下面的示例演示如何将事件处理程序附加到类的实例 Button
:The following example shows how event handlers are attached to instances of the Button
class:
public delegate void EventHandler(object sender, EventArgs e);
public class Button: Control
{
public event EventHandler Click;
}
public class LoginDialog: Form
{
Button OkButton;
Button CancelButton;
public LoginDialog() {
OkButton = new Button(...);
OkButton.Click += new EventHandler(OkButtonClick);
CancelButton = new Button(...);
CancelButton.Click += new EventHandler(CancelButtonClick);
}
void OkButtonClick(object sender, EventArgs e) {
// Handle OkButton.Click event
}
void CancelButtonClick(object sender, EventArgs e) {
// Handle CancelButton.Click event
}
}
此处, LoginDialog
实例构造函数创建两个 Button
实例,并将事件处理程序附加到 Click
事件。Here, the LoginDialog
instance constructor creates two Button
instances and attaches event handlers to the Click
events.
类似字段的事件Field-like events
在包含事件声明的类或结构的程序文本中,某些事件可以像字段一样使用。Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. 若要以这种方式使用,事件不得为 abstract
或 extern
,而且不能显式包含 event_accessor_declarations。To be used in this way, an event must not be abstract
or extern
, and must not explicitly include event_accessor_declarations. 此类事件可用于任何允许字段的上下文中。Such an event can be used in any context that permits a field. 该字段包含委托 (委托 ,) 引用已添加到事件的事件处理程序的列表。The field contains a delegate (Delegates) which refers to the list of event handlers that have been added to the event. 如果未添加任何事件处理程序,则字段将包含 null
。If no event handlers have been added, the field contains null
.
示例中In the example
public delegate void EventHandler(object sender, EventArgs e);
public class Button: Control
{
public event EventHandler Click;
protected void OnClick(EventArgs e) {
if (Click != null) Click(this, e);
}
public void Reset() {
Click = null;
}
}
Click
用作类中的字段 Button
。Click
is used as a field within the Button
class. 如示例所示,可以检查、修改字段,并将其用于委托调用表达式。As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. OnClick
类中的方法 Button
"引发" Click
事件。The OnClick
method in the Button
class "raises" the Click
event. 引发事件的概念恰恰等同于调用由事件表示的委托,因此,没有用于引发事件的特殊语言构造。The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events. 请注意,委托调用前面有一个检查,该检查可确保委托为非 null。Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.
在类的声明外部 Button
, Click
成员只能用于 +=
和运算符的左侧 -=
,如下所示Outside the declaration of the Button
class, the Click
member can only be used on the left-hand side of the +=
and -=
operators, as in
b.Click += new EventHandler(...);
这会将委托追加到事件的调用列表 Click
中,并which appends a delegate to the invocation list of the Click
event, and
b.Click -= new EventHandler(...);
这会从事件的调用列表中移除委托 Click
。which removes a delegate from the invocation list of the Click
event.
在编译类似于字段的事件时,编译器会自动创建存储来保存委托,并为事件创建访问器,以便在委托字段中添加或删除事件处理程序。When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field. 添加和移除操作是线程安全的,并且可能 (但不需要) 在将锁语句锁定 (锁语句) 在实例事件的包含对象上,或类型对象 (静态事件) 匿名对象创建表达式 。The addition and removal operations are thread safe, and may (but are not required to) be done while holding the lock (The lock statement) on the containing object for an instance event, or the type object (Anonymous object creation expressions) for a static event.
因此,以下形式的实例事件声明:Thus, an instance event declaration of the form:
class X
{
public event D Ev;
}
将编译为与等效的对象:will be compiled to something equivalent to:
class X
{
private D __Ev; // field to hold the delegate
public event D Ev {
add {
/* add the delegate in a thread safe way */
}
remove {
/* remove the delegate in a thread safe way */
}
}
}
在类中 X
,对 Ev
和运算符左侧的引用 +=
-=
会导致调用 add 和 remove 访问器。Within the class X
, references to Ev
on the left-hand side of the +=
and -=
operators cause the add and remove accessors to be invoked. 对的所有其他引用 Ev
将被编译为引用隐藏字段, __Ev
而不是 (成员访问) 。All other references to Ev
are compiled to reference the hidden field __Ev
instead (Member access). 名称 " __Ev
" 是任意名称; 隐藏的字段可能有任何名称,或者根本没有名称。The name "__Ev
" is arbitrary; the hidden field could have any name or no name at all.
事件访问器Event accessors
如上例所示,事件声明通常省略 event_accessor_declarations Button
。Event declarations typically omit event_accessor_declarations, as in the Button
example above. 执行此操作的一种情况是,这种情况涉及到每个事件的一个字段的存储成本是不可接受的。One situation for doing so involves the case in which the storage cost of one field per event is not acceptable. 在这种情况下,类可以包括 event_accessor_declarations ,并使用专用机制来存储事件处理程序列表。In such cases, a class can include event_accessor_declarations and use a private mechanism for storing the list of event handlers.
事件的 event_accessor_declarations 指定与添加和移除事件处理程序相关联的可执行语句。The event_accessor_declarations of an event specify the executable statements associated with adding and removing event handlers.
访问器声明由 add_accessor_declaration 和 remove_accessor_declaration 组成。The accessor declarations consist of an add_accessor_declaration and a remove_accessor_declaration. 每个访问器声明都包含标记 add
或 remove
后跟 块。Each accessor declaration consists of the token add
or remove
followed by a block. 与 add_accessor_declaration 关联的 块 指定在添加事件处理程序时要执行的语句,与 remove_accessor_declaration 关联的 块 指定在删除事件处理程序时要执行的语句。The block associated with an add_accessor_declaration specifies the statements to execute when an event handler is added, and the block associated with a remove_accessor_declaration specifies the statements to execute when an event handler is removed.
每个 add_accessor_declaration 和 remove_accessor_declaration 对应于一个方法,其中包含事件类型的单个值参数和一个 void
返回类型。Each add_accessor_declaration and remove_accessor_declaration corresponds to a method with a single value parameter of the event type and a void
return type. 事件访问器的隐式参数命名为 value
。The implicit parameter of an event accessor is named value
. 在事件分配中使用事件时,将使用适当的事件访问器。When an event is used in an event assignment, the appropriate event accessor is used. 具体而言,如果赋值运算符为 +=
,则使用 add 访问器,如果赋值运算符为,则 -=
使用 remove 访问器。Specifically, if the assignment operator is +=
then the add accessor is used, and if the assignment operator is -=
then the remove accessor is used. 在任一情况下,赋值运算符的右操作数用作事件访问器的参数。In either case, the right-hand operand of the assignment operator is used as the argument to the event accessor. Add_accessor_declaration 或 remove_accessor_declaration 的块必须符合 void
方法体中所述的方法规则。The block of an add_accessor_declaration or a remove_accessor_declaration must conform to the rules for void
methods described in Method body. 特别是, return
不允许这样的块中的语句指定表达式。In particular, return
statements in such a block are not permitted to specify an expression.
由于事件访问器隐式包含一个名为的参数 value
,因此在事件访问器中声明的局部变量或常量具有该名称是编译时错误。Since an event accessor implicitly has a parameter named value
, it is a compile-time error for a local variable or constant declared in an event accessor to have that name.
示例中In the example
class Control: Component
{
// Unique keys for events
static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();
// Return event handler associated with key
protected Delegate GetEventHandler(object key) {...}
// Add event handler associated with key
protected void AddEventHandler(object key, Delegate handler) {...}
// Remove event handler associated with key
protected void RemoveEventHandler(object key, Delegate handler) {...}
// MouseDown event
public event MouseEventHandler MouseDown {
add { AddEventHandler(mouseDownEventKey, value); }
remove { RemoveEventHandler(mouseDownEventKey, value); }
}
// MouseUp event
public event MouseEventHandler MouseUp {
add { AddEventHandler(mouseUpEventKey, value); }
remove { RemoveEventHandler(mouseUpEventKey, value); }
}
// Invoke the MouseUp event
protected void OnMouseUp(MouseEventArgs args) {
MouseEventHandler handler;
handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
if (handler != null)
handler(this, args);
}
}
Control
类实现事件的内部存储机制。the Control
class implements an internal storage mechanism for events. AddEventHandler
方法将委托值与键关联, GetEventHandler
方法返回当前与某个键相关联的委托,并且该方法将 RemoveEventHandler
委托移除为指定事件的事件处理程序。The AddEventHandler
method associates a delegate value with a key, the GetEventHandler
method returns the delegate currently associated with a key, and the RemoveEventHandler
method removes a delegate as an event handler for the specified event. 可能,基础存储机制的设计使委托值与键关联不会产生费用 null
,因此未处理的事件不会消耗存储空间。Presumably, the underlying storage mechanism is designed such that there is no cost for associating a null
delegate value with a key, and thus unhandled events consume no storage.
静态和实例事件Static and instance events
当事件声明包含修饰符时 static
,该事件被称为 *静态事件 _。When an event declaration includes a static
modifier, the event is said to be a *static event _. 如果不 static
存在修饰符,则该事件称为 _ 实例事件 *。When no static
modifier is present, the event is said to be an _*instance event**.
静态事件不与特定实例相关联,并且是 this
在静态事件的访问器中引用的编译时错误。A static event is not associated with a specific instance, and it is a compile-time error to refer to this
in the accessors of a static event.
实例事件与类的给定实例相关联,并且可以在该事件的访问 this
器中 (此访问) 访问此实例。An instance event is associated with a given instance of a class, and this instance can be accessed as this
(This access) in the accessors of that event.
当在窗体的 member_access (成员访问) 中引用事件时 E.M
,如果 M
是静态事件,则 E
必须表示包含的类型 M
,如果 M
是实例事件,则 E 必须表示包含的类型的实例 M
。When an event is referenced in a member_access (Member access) of the form E.M
, if M
is a static event, E
must denote a type containing M
, and if M
is an instance event, E must denote an instance of a type containing M
.
静态成员和实例成员之间的差异在 静态成员和实例成员中进行了进一步讨论。The differences between static and instance members are discussed further in Static and instance members.
虚拟、密封、重写和抽象事件访问器Virtual, sealed, override, and abstract event accessors
virtual
事件声明指定该事件的访问器是虚拟的。A virtual
event declaration specifies that the accessors of that event are virtual. virtual
修饰符适用于事件的两个访问器。The virtual
modifier applies to both accessors of an event.
abstract
事件声明指定事件的访问器是虚拟的,但不提供访问器的实际实现。An abstract
event declaration specifies that the accessors of the event are virtual, but does not provide an actual implementation of the accessors. 相反,非抽象派生类需要通过重写事件为访问器提供自己的实现。Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the event. 因为抽象事件声明不提供实际的实现,所以它不能提供用括号分隔的 event_accessor_declarations。Because an abstract event declaration provides no actual implementation, it cannot provide brace-delimited event_accessor_declarations.
同时包含和修饰符的事件声明 abstract
override
指定事件是抽象的,并且会重写基事件。An event declaration that includes both the abstract
and override
modifiers specifies that the event is abstract and overrides a base event. 此类事件的访问器也是抽象的。The accessors of such an event are also abstract.
仅在抽象类 (抽象 类) 中允许抽象事件声明。Abstract event declarations are only permitted in abstract classes (Abstract classes).
通过包含指定修饰符的事件声明,可在派生类中重写继承的虚拟事件的访问器 override
。The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override
modifier. 这称为 重写事件声明。This is known as an overriding event declaration. 重写事件声明未声明新事件。An overriding event declaration does not declare a new event. 相反,它只是专用化现有虚拟事件的访问器的实现。Instead, it simply specializes the implementations of the accessors of an existing virtual event.
重写事件声明必须指定与重写事件完全相同的可访问性修饰符、类型和名称。An overriding event declaration must specify the exact same accessibility modifiers, type, and name as the overridden event.
重写事件声明可能包括 sealed
修饰符。An overriding event declaration may include the sealed
modifier. 使用此修饰符可防止派生类进一步重写事件。Use of this modifier prevents a derived class from further overriding the event. 密封事件的访问器也是密封的。The accessors of a sealed event are also sealed.
重写事件声明包含修饰符是编译时错误 new
。It is a compile-time error for an overriding event declaration to include a new
modifier.
除了声明和调用语法中的差异外,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. abstract
和 override
修饰符可一起使用,以便抽象索引器可以重写虚拟一个。The abstract
and override
modifiers may be used together so that an abstract indexer can override a virtual one.
索引器声明的 类型 指定声明引入的索引器的元素类型。The type of an indexer declaration specifies the element type of the indexer introduced by the declaration. 除非索引器是显式接口成员实现,否则该 类型 后跟关键字 this
。Unless the indexer is an explicit interface member implementation, the type is followed by the keyword this
. 对于显式接口成员实现,该 类型 后跟一个 interface_type、" .
" 和关键字 this
。For an explicit interface member implementation, the type is followed by an interface_type, a ".
", and the keyword this
. 与其他成员不同,索引器没有用户定义的名称。Unlike other members, indexers do not have user-defined names.
Formal_parameter_list 指定索引器的参数。The formal_parameter_list specifies the parameters of the indexer. 索引器的形参列表与 (方法参数) 方法的形参列表相同,不同之处在于至少必须指定一个形参,并且 ref
out
不允许和参数修饰符。The formal parameter list of an indexer corresponds to that of a method (Method parameters), except that at least one parameter must be specified, and that the ref
and out
parameter modifiers are not permitted.
索引器的 类型 和 formal_parameter_list 中引用的每个类型必须至少与索引器本身 (可访问 性约束) 相同。The type of an indexer and each of the types referenced in the formal_parameter_list must be at least as accessible as the indexer itself (Accessibility constraints).
Indexer_body 可以是由 访问器体 _ 或 _表达式体*_ 组成的。An indexer_body may either consist of an accessor body _ or an _expression body*_. 在访问器主体中,_accessor_declarations * (必须括在 " {
" 和 " }
" 标记中)声明属性的访问器 (访问 器) 。In an accessor body, _accessor_declarations*, which must be enclosed in "{
" and "}
" tokens, declare the accessors (Accessors) of the property. 访问器指定与读取和写入属性相关联的可执行语句。The accessors specify the executable statements associated with reading and writing the property.
=>
由 "" 后跟表达式和分号的表达式体 E
完全等效于语句体 { get { return E; } }
,因此只能用于指定仅限 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. 因此,不能将索引器元素作为 ref
或 out
参数传递。Thus, it is not possible to pass an indexer element as a ref
or out
argument.
索引器的形参列表定义索引器的签名 (签名和重载) 。The formal parameter list of an indexer defines the signature (Signatures and overloading) of the indexer. 具体而言,索引器的签名由其形参的数量和类型组成。Specifically, the signature of an indexer consists of the number and types of its formal parameters. 形参的元素类型和名称不属于索引器的签名。The element type and names of the formal parameters are not part of an indexer's signature.
索引器的签名必须与同一类中声明的所有其他索引器的签名不同。The signature of an indexer must differ from the signatures of all other indexers declared in the same class.
索引器和属性在概念上非常类似,但在以下方面有所不同:Indexers and properties are very similar in concept, but differ in the following ways:
- 属性由其名称标识,而索引器由其签名标识。A property is identified by its name, whereas an indexer is identified by its signature.
- 可以通过 simple_name (简单名称) 或 member_access (成员访问) 来访问属性,而索引器元素则通过 element_access (索引器访问) 进行访问。A property is accessed through a simple_name (Simple names) or a member_access (Member access), whereas an indexer element is accessed through an element_access (Indexer access).
- 属性可以是
static
成员,而索引器始终是实例成员。A property can be astatic
member, whereas an indexer is always an instance member. get
属性的访问器对应于不带参数的方法,而索引器的get
访问器对应于具有与索引器相同的形参列表的方法。Aget
accessor of a property corresponds to a method with no parameters, whereas aget
accessor of an indexer corresponds to a method with the same formal parameter list as the indexer.set
属性的访问器对应于带有名为的单个参数的方法value
,而索引器的set
访问器对应于一个方法,该方法具有与索引器相同的形参列表,另外还有一个名为的附加参数value
。Aset
accessor of a property corresponds to a method with a single parameter namedvalue
, whereas aset
accessor of an indexer corresponds to a method with the same formal parameter list as the indexer, plus an additional parameter namedvalue
.- 索引器访问器使用与索引器参数相同的名称声明局部变量是编译时错误。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 syntaxbase.P
, whereP
is the property name. 在重写索引器声明中,继承的索引器是使用语法访问的base[E]
,其中,E
是逗号分隔的表达式列表。In an overriding indexer declaration, the inherited indexer is accessed using the syntaxbase[E]
, whereE
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:
- 运算符声明必须同时包含
public
和static
修饰符。An operator declaration must include both apublic
and astatic
modifier. - 运算符的参数 (s) 必须是值参数 (值参数) 。The parameter(s) of an operator must be value parameters (Value parameters). 运算符声明要指定或参数,这是编译时错误
ref
out
。It is a compile-time error for an operator declaration to specifyref
orout
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 typeT
orT?
and can return any type. - 一元
++
或--
运算符必须采用或类型的单个参数T
T?
,并且必须返回该相同类型或从其派生的类型。A unary++
or--
operator must take a single parameter of typeT
orT?
and must return that same type or a type derived from it. - 一元
true
或false
运算符必须采用或类型的单个参数T
T?
,并且必须返回类型bool
。A unarytrue
orfalse
operator must take a single parameter of typeT
orT?
and must return typebool
.
一元运算符的签名由运算符标记 (、、、、、、 +
-
!
~
++
--
true
或 false
) 以及单个形参的类型组成。The signature of a unary operator consists of the operator token (+
, -
, !
, ~
, ++
, --
, true
, or false
) and the type of the single formal parameter. 返回类型不是一元运算符的签名的一部分,也不是形参的名称。The return type is not part of a unary operator's signature, nor is the name of the formal parameter.
true
和 false
一元运算符要求成对声明。The true
and false
unary operators require pair-wise declaration. 如果类声明了这些运算符中的一个,而没有同时声明其他运算符,则会发生编译时错误。A compile-time error occurs if a class declares one of these operators without also declaring the other. true
false
用户定义的条件逻辑运算符和布尔表达式中进一步介绍了和运算符。The true
and false
operators are described further in User-defined conditional logical operators and Boolean expressions.
下面的示例演示了一个整数向量类的实现和后续使用 operator ++
:The following example shows an implementation and subsequent usage of operator ++
for an integer vector class:
public class IntVector
{
public IntVector(int length) {...}
public int Length {...} // read-only property
public int this[int index] {...} // read-write indexer
public static IntVector operator ++(IntVector iv) {
IntVector temp = new IntVector(iv.Length);
for (int i = 0; i < iv.Length; i++)
temp[i] = iv[i] + 1;
return temp;
}
}
class Test
{
static void Main() {
IntVector iv1 = new IntVector(4); // vector of 4 x 0
IntVector iv2;
iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1
iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2
}
}
请注意,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:
- 二元非移位运算符必须采用两个参数,其中至少一个参数必须具有类型
T
或T?
,并且可以返回任何类型。A binary non-shift operator must take two parameters, at least one of which must have typeT
orT?
, and can return any type. - 二元
<<
或>>
运算符必须采用两个参数,其中第一个参数的类型必须为,第二个参数必须T
T?
具有类型int
或int?
,并且可以返回任何类型。A binary<<
or>>
operator must take two parameters, the first of which must have typeT
orT?
and the second of which must have typeint
orint?
, 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 ==
andoperator !=
operator >
和operator <
operator >
andoperator <
operator >=
和operator <=
operator >=
andoperator <=
转换运算符Conversion operators
转换运算符声明将 用户定义的转换 引入 (用户定义 的转换,) 增加预定义的隐式和显式转换。A conversion operator declaration introduces a user-defined conversion (User-defined conversions) which augments the pre-defined implicit and explicit conversions.
包含关键字的转换运算符声明 implicit
引入了用户定义的隐式转换。A conversion operator declaration that includes the implicit
keyword introduces a user-defined implicit conversion. 隐式转换可能会在多种情况下发生,包括函数成员调用、强制转换表达式和赋值。Implicit conversions can occur in a variety of situations, including function member invocations, cast expressions, and assignments. 隐式转换中对此进行了进一步说明。This is described further in Implicit conversions.
包含关键字的转换运算符声明 explicit
引入了用户定义的显式转换。A conversion operator declaration that includes the explicit
keyword introduces a user-defined explicit conversion. 显式转换可在强制转换表达式中发生,并在 显式转换中进一步说明。Explicit conversions can occur in cast expressions, and are described further in Explicit conversions.
转换运算符从转换运算符的参数类型指示的源类型转换为目标类型,由转换运算符的返回类型指示。A conversion operator converts from a source type, indicated by the parameter type of the conversion operator, to a target type, indicated by the return type of the conversion operator.
对于给定的源类型 S
和目标类型 T
,如果 S
或 T
是可以为 null 的类型,则让 S0
和 T0
引用它们的基础类型,否则, S0
并 T0
S
T
分别为和。For a given source type S
and target type T
, if S
or T
are nullable types, let S0
and T0
refer to their underlying types, otherwise S0
and T0
are equal to S
and T
respectively. S
T
仅当满足以下所有条件时,才允许类或结构声明从源类型到目标类型的转换:A class or struct is permitted to declare a conversion from a source type S
to a target type T
only if all of the following are true:
S0
和T0
是不同的类型。S0
andT0
are different types.S0
或T0
是发生运算符声明的类或结构类型。EitherS0
orT0
is the class or struct type in which the operator declaration takes place.S0
和T0
都不是 interface_type。NeitherS0
norT0
is an interface_type.- 如果不包括用户定义的转换,则从到的转换不存在
S
T
T
S
。Excluding user-defined conversions, a conversion does not exist fromS
toT
or fromT
toS
.
对于这些规则,与或关联的任何类型参数 S
都被 T
视为唯一的类型,这些类型与其他类型没有继承关系,并忽略这些类型参数上的任何约束。For the purposes of these rules, any type parameters associated with S
or T
are considered to be unique types that have no inheritance relationship with other types, and any constraints on those type parameters are ignored.
示例中In the example
class C<T> {...}
class D<T>: C<T>
{
public static implicit operator C<int>(D<T> value) {...} // Ok
public static implicit operator C<string>(D<T> value) {...} // Ok
public static implicit operator C<T>(D<T> value) {...} // Error
}
允许使用前两个运算符声明,因为对于 索引器.3,和 T
分别被 int
string
视为唯一的类型而没有关系。the first two operator declarations are permitted because, for the purposes of Indexers.3, T
and int
and string
respectively are considered unique types with no relationship. 但是,第三个运算符是错误,因为 C<T>
是的基类 D<T>
。However, the third operator is an error because C<T>
is the base class of D<T>
.
从第二个规则开始,转换运算符必须在声明运算符的类或结构类型中转换为或。From the second rule it follows that a conversion operator must convert either to or from the class or struct type in which the operator is declared. 例如,类或结构类型可以 C
定义从 C
到 int
和到之间的转换 int
C
,但不 int
bool
能定义从到的转换。For example, it is possible for a class or struct type C
to define a conversion from C
to int
and from int
to C
, but not from int
to bool
.
不能直接重新定义预定义的转换。It is not possible to directly redefine a pre-defined conversion. 因此,不允许转换运算符从或转换为, object
因为 object
和所有其他类型之间已存在隐式和显式转换。Thus, conversion operators are not allowed to convert from or to object
because implicit and explicit conversions already exist between object
and all other types. 同样,转换的源类型和目标类型都不能是另一类型的基类型,因为转换已经存在。Likewise, neither the source nor the target types of a conversion can be a base type of the other, since a conversion would then already exist.
但是,可以在针对特定类型参数的泛型类型上声明运算符,指定已作为预定义转换存在的转换。However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions. 示例中In the example
struct Convertible<T>
{
public static implicit operator Convertible<T>(T value) {...}
public static explicit operator T(Convertible<T> value) {...}
}
当 type object
指定为的类型参数时 T
,第二个运算符将声明已存在 (隐式的转换,因此,从任何类型到类型) 的显式转换也是如此 object
。when type object
is specified as a type argument for T
, the second operator declares a conversion that already exists (an implicit, and therefore also an explicit, conversion exists from any type to type object
).
如果两种类型之间存在预定义的转换,则将忽略这些类型之间的任何用户定义的转换。In cases where a pre-defined conversion exists between two types, any user-defined conversions between those types are ignored. 具体而言:Specifically:
- 如果) 存在从类型到类型的预定义隐式转换 (隐 式转换
S
T
,S
则将忽略从到的隐式或显式) (的所有用户定义的转换T
。If a pre-defined implicit conversion (Implicit conversions) exists from typeS
to typeT
, all user-defined conversions (implicit or explicit) fromS
toT
are ignored. - 如果从类型到类型的预定义显式转换 (显式 转换) 存在
S
T
,则将忽略从到的任何用户定义的显式转换S
T
。If a pre-defined explicit conversion (Explicit conversions) exists from typeS
to typeT
, any user-defined explicit conversions fromS
toT
are ignored. 此外:Furthermore:
如果 T
是接口类型,则将忽略从到的用户定义的隐式转换 S
T
。If T
is an interface type, user-defined implicit conversions from S
to T
are ignored.
否则, S
仍会考虑从到的用户定义的隐式转换 T
。Otherwise, user-defined implicit conversions from S
to T
are still considered.
对于所有类型 object
,但以上类型声明的运算符 Convertible<T>
不会与预定义转换冲突。For all types but object
, the operators declared by the Convertible<T>
type above do not conflict with pre-defined conversions. 例如:For example:
void F(int i, Convertible<int> n) {
i = n; // Error
i = (int)n; // User-defined explicit conversion
n = i; // User-defined implicit conversion
n = (Convertible<int>)i; // User-defined implicit conversion
}
但对于类型 object
,预定义的转换在所有情况下都会隐藏用户定义的转换,但会隐藏其中一项:However, for type object
, pre-defined conversions hide the user-defined conversions in all cases but one:
void F(object o, Convertible<object> n) {
o = n; // Pre-defined boxing conversion
o = (object)n; // Pre-defined boxing conversion
n = o; // User-defined implicit conversion
n = (Convertible<object>)o; // Pre-defined unboxing conversion
}
不允许用户定义的转换从或转换到 interface_type。User-defined conversions are not allowed to convert from or to interface_type s. 特别是,此限制可确保在转换为 interface_type 时不会发生用户定义的转换,并且仅当被转换的对象真正实现指定的 interface_type 时,才能成功转换为 interface_type 。In particular, this restriction ensures that no user-defined transformations occur when converting to an interface_type, and that a conversion to an interface_type succeeds only if the object being converted actually implements the specified interface_type.
转换运算符的签名包含源类型和目标类型。The signature of a conversion operator consists of the source type and the target type. (请注意,这是返回类型参与签名的唯一成员窗体。 ) implicit
explicit
转换运算符的或分类不是运算符签名的一部分。(Note that this is the only form of member for which the return type participates in the signature.) The implicit
or explicit
classification of a conversion operator is not part of the operator's signature. 因此,类或结构不能同时声明 implicit
explicit
具有相同源和目标类型的和转换运算符。Thus, a class or struct cannot declare both an implicit
and an explicit
conversion operator with the same source and target types.
通常,用户定义的隐式转换应设计为从不引发异常,并且永远不会丢失信息。In general, user-defined implicit conversions should be designed to never throw exceptions and never lose information. 例如,如果用户定义的转换可以给出异常 (例如,因为 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
可能值的子集 byte
。the conversion from Digit
to byte
is implicit because it never throws exceptions or loses information, but the conversion from byte
to Digit
is explicit since Digit
can only represent a subset of the possible values of a byte
.
实例构造函数Instance constructors
*实例构造函数 _ 是一个成员,用于实现初始化类的实例所需的操作。An *instance constructor _ is a member that implements the actions required to initialize an instance of a class. 实例构造函数使用 _constructor_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 formbase(argument_list)
orbase()
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 formthis(argument-list)
orthis()
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;
}
}
包含若干变量初始值设定项;它还包含两个窗体的构造函数初始值设定项 (base
和 this
) 。contains several variable initializers; it also contains constructor initializers of both forms (base
and this
). 该示例与下面显示的代码相对应,其中每个注释指示自动插入的语句 (用于自动插入的构造函数调用的语法无效,但仅用于说明) 的机制。The example corresponds to the code shown below, where each comment indicates an automatically inserted statement (the syntax used for the automatically inserted constructor invocations isn't valid, but merely serves to illustrate the mechanism).
using System.Collections;
class A
{
int x, y, count;
public A() {
x = 1; // Variable initializer
y = -1; // Variable initializer
object(); // Invoke object() constructor
count = 0;
}
public A(int n) {
x = 1; // Variable initializer
y = -1; // Variable initializer
object(); // Invoke object() constructor
count = n;
}
}
class B: A
{
double sqrt2;
ArrayList items;
int max;
public B(): this(100) {
B(100); // Invoke B(int) constructor
items.Add("default");
}
public B(int n): base(n - 1) {
sqrt2 = Math.Sqrt(2.0); // Variable initializer
items = new ArrayList(100); // Variable initializer
A(n - 1); // Invoke A(int) constructor
max = n;
}
}
默认构造函数Default constructors
如果类不包含实例构造函数声明,则会自动提供一个默认实例构造函数。If a class contains no instance constructor declarations, a default instance constructor is automatically provided. 该默认构造函数只调用直接基类的无参数构造函数。That default constructor simply invokes the parameterless constructor of the direct base class. 如果类是抽象类,则对默认构造函数的声明的可访问性是受保护的。If the class is abstract then the declared accessibility for the default constructor is protected. 否则,默认构造函数的声明的可访问性是公共的。Otherwise, the declared accessibility for the default constructor is public. 因此,默认构造函数始终为形式Thus, the default constructor is always of the form
protected C(): base() {}
或or
public C(): base() {}
其中 C
,为类的名称。where C
is the name of the class. 如果重载决策无法确定基类构造函数初始值设定项的唯一最佳候选项,则会发生编译时错误。If overload resolution is unable to determine a unique best candidate for the base class constructor initializer then a compile-time error occurs.
示例中In the example
class Message
{
object sender;
string text;
}
由于类不包含实例构造函数声明,因此提供了默认的构造函数。a default constructor is provided because the class contains no instance constructor declarations. 因此,该示例完全等效于Thus, the example is precisely equivalent to
class Message
{
object sender;
string text;
public Message(): base() {}
}
私有构造函数Private constructors
如果某个类 T
只声明了私有实例构造函数,则不能从的程序文本外的类 T
派生 T
或直接创建的实例 T
。When a class T
declares only private instance constructors, it is not possible for classes outside the program text of T
to derive from T
or to directly create instances of T
. 因此,如果某个类只包含静态成员,并且不应实例化,则添加一个空的私有实例构造函数会阻止实例化。Thus, if a class contains only static members and isn't intended to be instantiated, adding an empty private instance constructor will prevent instantiation. 例如:For example:
public class Trig
{
private Trig() {} // Prevent instantiation
public const double PI = 3.14159265358979323846;
public static double Sin(double x) {...}
public static double Cos(double x) {...}
public static double Tan(double x) {...}
}
Trig
类将相关方法和常数分组,但不能进行实例化。The Trig
class groups related methods and constants, but is not intended to be instantiated. 因此,它声明了一个空的私有实例构造函数。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_body , void
(方法体) 的返回类型。This corresponds exactly to the method_body of a static method with a void
return type (Method body).
静态构造函数不是继承的,不能直接调用。Static constructors are not inherited, and cannot be called directly.
封闭式类类型的静态构造函数在给定的应用程序域中最多执行一次。The static constructor for a closed class type executes at most once in a given application domain. 在应用程序域中,以下事件的第一个事件会触发静态构造函数的执行:The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
- 创建类类型的实例。An instance of the class type is created.
- 引用类类型的任何静态成员。Any of the static members of the class type are referenced.
如果类包含 Main
方法 (应用程序启动) 在其中开始执行,则在调用方法之前,将执行该类的静态构造函数 Main
。If a class contains the Main
method (Application Startup) in which execution begins, the static constructor for that class executes before the Main
method is called.
若要初始化新的封闭式类类型,请先创建一组新的静态字段 (静态字段和实例字段 ,) 为该特定已关闭类型创建。To initialize a new closed class type, first a new set of static fields (Static and instance fields) for that particular closed type is created. 每个静态字段都初始化为其默认值 (默认 值) 。Each of the static fields is initialized to its default value (Default values). 接下来,将为这些静态字段 (执行静态字段 初始化) 静态字段初始值设定项。Next, the static field initializers (Static field initialization) are executed for those static fields. 最后,执行静态构造函数。Finally, the static constructor is executed.
示例The example
using System;
class Test
{
static void Main() {
A.F();
B.F();
}
}
class A
{
static A() {
Console.WriteLine("Init A");
}
public static void F() {
Console.WriteLine("A.F");
}
}
class B
{
static B() {
Console.WriteLine("Init B");
}
public static void F() {
Console.WriteLine("B.F");
}
}
必须生成输出:must produce the output:
Init A
A.F
Init B
B.F
由于的 A
静态构造函数的执行由对的调用触发,因此,由对的 A.F
调用触发了的 B
静态构造函数的执行 B.F
。because the execution of A
's static constructor is triggered by the call to A.F
, and the execution of B
's static constructor is triggered by the call to B.F
.
可以构造循环依赖关系,以便在其默认值状态中观察包含变量初始值设定项的静态字段。It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state.
示例The example
using System;
class A
{
public static int X;
static A() {
X = B.Y + 1;
}
}
class B
{
public static int Y = A.X + 1;
static B() {}
static void Main() {
Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);
}
}
生成输出produces the output
X = 1, Y = 2
若要执行 Main
方法,系统首先在 B.Y
类 B
的静态构造函数之前运行的初始化表达式。To execute the Main
method, the system first runs the initializer for B.Y
, prior to class B
's static constructor. Y
的初始值设定项会导致 A
运行静态构造函数,因为引用的值 A.X
。Y
's initializer causes A
's static constructor to be run because the value of A.X
is referenced. 的静态构造函数 A
反过来会继续计算的值 X
,并且在执行此操作时,将提取的默认值 Y
,即零。The static constructor of A
in turn proceeds to compute the value of X
, and in doing so fetches the default value of Y
, which is zero. A.X
因此初始化为1。A.X
is thus initialized to 1. 运行 A
的静态字段初始值设定项和静态构造函数的过程随后完成,返回的初始值的计算 Y
结果为2。The process of running A
's static field initializers and static constructor then completes, returning to the calculation of the initial value of Y
, the result of which becomes 2.
由于静态构造函数仅对每个封闭式构造类类型执行一次,因此,在不能通过约束 (类型参数约束) 的情况下,强制对类型参数强制执行运行时检查。Because the static constructor is executed exactly once for each closed constructed class type, it is a convenient place to enforce run-time checks on the type parameter that cannot be checked at compile-time via constraints (Type parameter constraints). 例如,下面的类型使用静态构造函数来强制类型参数是一个枚举:For example, the following type uses a static constructor to enforce that the type argument is an enum:
class Gen<T> where T: struct
{
static Gen() {
if (!typeof(T).IsEnum) {
throw new ArgumentException("T must be an enum");
}
}
}
析构函数Destructors
*析构函数 _ 是实现析构类的实例所需的操作的成员。A *destructor _ is a member that implements the actions required to destruct an instance of a class. 使用 _destructor_declaration * 声明析构函数:A destructor is declared using a _destructor_declaration*:
destructor_declaration
: attributes? 'extern'? '~' identifier '(' ')' destructor_body
| destructor_declaration_unsafe
;
destructor_body
: block
| ';'
;
Destructor_declaration 可以包含一组 属性) (属性。A destructor_declaration may include a set of attributes (Attributes).
Destructor_declaration 的 标识符 必须命名声明析构函数的类。The identifier of a destructor_declaration must name the class in which the destructor is declared. 如果指定了其他名称,则会发生编译时错误。If any other name is specified, a compile-time error occurs.
当析构函数声明包含 extern
修饰符时,析构函数被称为 *外部析构函数 _。When a destructor declaration includes an extern
modifier, the destructor is said to be an *external destructor _. 由于外部析构函数声明不提供实际的实现,因此它的 _destructor_body * 由分号组成。Because an external destructor declaration provides no actual implementation, its _destructor_body* consists of a semicolon. 对于所有其他析构函数, destructor_body 包含一个 块 ,它指定要执行的语句,以便销毁类的实例。For all other destructors, the destructor_body consists of a block which specifies the statements to execute in order to destruct an instance of the class. Destructor_body 完全对应于具有返回类型的实例方法的 method_body void
(方法体) 。A destructor_body corresponds exactly to the method_body of an instance method with a void
return type (Method body).
析构函数不是继承的。Destructors are not inherited. 因此,一个类不包含析构函数,而析构函数可能在该类中声明。Thus, a class has no destructors other than the one which may be declared in that class.
由于析构函数不需要任何参数,因此不能重载,因此一个类最多只能有一个析构函数。Since a destructor is required to have no parameters, it cannot be overloaded, so a class can have, at most, one destructor.
析构函数是自动调用的,不能被显式调用。Destructors are invoked automatically, and cannot be invoked explicitly. 当某个实例对于任何代码都无法使用该实例时,该实例将可用于销毁。An instance becomes eligible for destruction when it is no longer possible for any code to use that instance. 在实例符合析构条件后,可能会在任何时间执行实例的析构函数。Execution of the destructor for the instance may occur at any time after the instance becomes eligible for destruction. 当实例销毁时,该实例的继承链中的析构函数将按顺序调用,从最常派生到最小派生。When an instance is destructed, the destructors in that instance's inheritance chain are called, in order, from most derived to least derived. 析构函数可以在任何线程上执行。A destructor may be executed on any thread. 若要进一步讨论控制何时以及如何执行析构函数,请参阅 自动内存管理。For further discussion of the rules that govern when and how a destructor is executed, see Automatic memory management.
示例的输出The output of the example
using System;
class A
{
~A() {
Console.WriteLine("A's destructor");
}
}
class B: A
{
~B() {
Console.WriteLine("B's destructor");
}
}
class Test
{
static void Main() {
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
是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.Object
。Destructors are implemented by overriding the virtual method Finalize
on System.Object
. 不允许 c # 程序重写此方法,或将其调用 (或直接) 其重写。C# programs are not permitted to override this method or call it (or overrides of it) directly. 例如,程序For instance, the program
class A
{
override protected void Finalize() {} // error
public void F() {
this.Finalize(); // error
}
}
包含两个错误。contains two errors.
编译器的行为就像此方法和它的替代一样,根本就不存在。The compiler behaves as if this method, and overrides of it, do not exist at all. 因此,此程序:Thus, this program:
class A
{
void Finalize() {} // permitted
}
有效,并显示的方法隐藏了 System.Object
的 Finalize
方法。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_body、 operator_body 或 accessor_body 出现,而事件、实例构造函数、静态构造函数和析构函数不能作为迭代器实现。It can occur as a method_body, operator_body or accessor_body, whereas events, instance constructors, static constructors and destructors cannot be implemented as iterators.
使用迭代器块实现函数成员时,函数成员的形参表的编译时错误是指定任何 ref
或 out
参数。When a function member is implemented using an iterator block, it is a compile-time error for the formal parameter list of the function member to specify any ref
or out
parameters.
枚举器接口Enumerator interfaces
枚举器接口 是非泛型接口 System.Collections.IEnumerator
和泛型接口的所有实例化 System.Collections.Generic.IEnumerator<T>
。The enumerator interfaces are the non-generic interface System.Collections.IEnumerator
and all instantiations of the generic interface System.Collections.Generic.IEnumerator<T>
. 为了简单起见,在本章中,这些接口分别作为和引用 IEnumerator
IEnumerator<T>
。For the sake of brevity, in this chapter these interfaces are referenced as IEnumerator
and IEnumerator<T>
, respectively.
可枚举接口Enumerable interfaces
可 枚举接口 是非泛型接口 System.Collections.IEnumerable
和泛型接口的所有实例化 System.Collections.Generic.IEnumerable<T>
。The enumerable interfaces are the non-generic interface System.Collections.IEnumerable
and all instantiations of the generic interface System.Collections.Generic.IEnumerable<T>
. 为了简单起见,在本章中,这些接口分别作为和引用 IEnumerable
IEnumerable<T>
。For the sake of brevity, in this chapter these interfaces are referenced as IEnumerable
and IEnumerable<T>
, respectively.
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
IEnumerable
为object
。The yield type of an iterator that returnsIEnumerator
orIEnumerable
isobject
. - 返回或的迭代器的 yield 类型
IEnumerator<T>
IEnumerable<T>
为T
。The yield type of an iterator that returnsIEnumerator<T>
orIEnumerable<T>
isT
.
枚举器对象Enumerator objects
当使用迭代器块实现返回枚举器接口类型的函数成员时,调用函数成员不会立即执行迭代器块中的代码。When a function member returning an enumerator interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. 相反,将创建并返回一个 枚举器对象 。Instead, an enumerator object is created and returned. 此对象封装在迭代器块中指定的代码,并在调用枚举器对象的方法时,在迭代器块中执行代码 MoveNext
。This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object's MoveNext
method is invoked. 枚举器对象具有以下特征:An enumerator object has the following characteristics:
- 它实现
IEnumerator
和IEnumerator<T>
,其中T
是迭代器的 yield 类型。It implementsIEnumerator
andIEnumerator<T>
, whereT
is the yield type of the iterator. - 它实现
System.IDisposable
。It implementsSystem.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
取决于调用时枚举器对象的状态 MoveNext
:The precise action performed by MoveNext
depends on the state of the enumerator object when MoveNext
is invoked:
- 如果枚举器对象的状态在 之前,则调用
MoveNext
:If the state of the enumerator object is before, invokingMoveNext
:- 将状态更改为 正在运行。Changes the state to running.
- 初始化参数 (包括
this
迭代器块) 到参数值和初始化枚举器对象时保存的实例值。Initializes the parameters (includingthis
) 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 invokingMoveNext
is unspecified. - 如果枚举器对象的状态为 "已 挂起",则调用
MoveNext
:If the state of the enumerator object is suspended, invokingMoveNext
:- 将状态更改为 正在运行。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 theyield return
statement that caused the suspension of execution and continues until execution is interrupted (as described below).
- 如果枚举数对象的状态为 after,则调用
MoveNext
返回false
。If the state of the enumerator object is after, invokingMoveNext
returnsfalse
.
当 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 ayield 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 theCurrent
property of the enumerator object. - 迭代器主体的执行将被挂起。Execution of the iterator body is suspended. 保存) 的所有局部变量和参数的值 (包括
this
此语句的位置yield return
。The values of all local variables and parameters (includingthis
) are saved, as is the location of thisyield return
statement. 如果yield return
语句在一个或多个try
块中,则finally
不会执行关联的块。If theyield return
statement is within one or moretry
blocks, the associatedfinally
blocks are not executed at this time. - 枚举器对象的状态将更改为 "已 挂起"。The state of the enumerator object is changed to suspended.
MoveNext
方法返回true
到其调用方,指示迭代已成功地推进到下一个值。TheMoveNext
method returnstrue
to its caller, indicating that the iteration successfully advanced to the next value.
- 计算语句中给定的表达式,将其隐式转换为 yield 类型,并将其分配给
- 当
yield break
) yield 语句 (遇到语句时:When ayield break
statement is encountered (The yield statement):- 如果
yield break
语句在一个或多个块中,则将try
执行关联的finally
块。If theyield break
statement is within one or moretry
blocks, the associatedfinally
blocks are executed. - 枚举器对象的状态将更改为 after。The state of the enumerator object is changed to after.
MoveNext
方法返回false
到其调用方,指示迭代已完成。TheMoveNext
method returnsfalse
to its caller, indicating that the iteration is complete.
- 如果
- 当遇到迭代器主体的末尾时:When the end of the iterator body is encountered:
- 枚举器对象的状态将更改为 after。The state of the enumerator object is changed to after.
MoveNext
方法返回false
到其调用方,指示迭代已完成。TheMoveNext
method returnsfalse
to its caller, indicating that the iteration is complete.
- 当引发异常并将其传播到迭代器块外时:When an exception is thrown and propagated out of the iterator block:
finally
迭代器正文中的相应块将由异常传播执行。Appropriatefinally
blocks in the iterator body will have been executed by the exception propagation.- 枚举器对象的状态将更改为 after。The state of the enumerator object is changed to after.
- 异常传播继续到方法的调用方
MoveNext
。The exception propagation continues to the caller of theMoveNext
method.
当前属性The Current property
枚举器对象的 Current
属性受 yield return
迭代器块中的语句影响。An enumerator object's Current
property is affected by yield return
statements in the iterator block.
如果枚举器对象处于 *挂起 _ 状态,则的值 Current
是由之前对的调用设置的值 MoveNext
。When an enumerator object is in the *suspended _ state, the value of Current
is the value set by the previous call to MoveNext
. 如果枚举器对象位于 " *之前*"、" *正在运行*" 或 "_" 状态 *之后*,则 Current
不指定访问结果。When an enumerator object is in the before, running, or _ after* states, the result of accessing Current
is unspecified.
对于 yield 类型不是的迭代器 object
, Current
通过枚举器对象的实现访问的结果 IEnumerable
对应于 Current
通过枚举器对象的实现进行访问 IEnumerator<T>
,并将结果转换为 object
。For an iterator with a yield type other than object
, the result of accessing Current
through the enumerator object's IEnumerable
implementation corresponds to accessing Current
through the enumerator object's IEnumerator<T>
implementation and casting the result to object
.
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 _, invokingDispose
changes the state to _after**. - 如果枚举器对象的状态为 正在运行,则调用的结果
Dispose
是未指定的。If the state of the enumerator object is running, the result of invokingDispose
is unspecified. - 如果枚举器对象的状态为 "已 挂起",则调用
Dispose
:If the state of the enumerator object is suspended, invokingDispose
:- 将状态更改为 正在运行。Changes the state to running.
- 执行所有 finally 块,就好像最后执行的
yield return
语句是yield break
语句一样。Executes any finally blocks as if the last executedyield return
statement were ayield break
statement. 如果这导致引发异常并将其传播到迭代器主体外,则枚举器对象的状态将设置为 after ,并将异常传播到方法的调用方Dispose
。If this causes an exception to be thrown and propagated out of the iterator body, the state of the enumerator object is set to after and the exception is propagated to the caller of theDispose
method. - 将状态更改为 after。Changes the state to after.
- 如果枚举数对象的状态为 after,则调用
Dispose
不会有任何影响。If the state of the enumerator object is after, invokingDispose
has no affect.
可枚举对象Enumerable objects
当使用迭代器块实现返回可枚举接口类型的函数成员时,调用函数成员不会立即执行迭代器块中的代码。When a function member returning an enumerable interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. 相反,将创建并返回一个可 枚举对象 。Instead, an enumerable object is created and returned. 可枚举对象的 GetEnumerator
方法返回一个枚举器对象,该对象封装迭代器块中指定的代码,并在调用枚举器对象的方法时,在迭代器块中执行代码 MoveNext
。The enumerable object's GetEnumerator
method returns an enumerator object that encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object's MoveNext
method is invoked. 可枚举对象具有以下特征:An enumerable object has the following characteristics:
- 它实现
IEnumerable
和IEnumerable<T>
,其中T
是迭代器的 yield 类型。It implementsIEnumerable
andIEnumerable<T>
, whereT
is the yield type of the iterator. - 它使用参数值的副本进行初始化, (如果任何) 和实例值传递到函数成员。It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
可枚举对象通常是编译器生成的可枚举类的实例,它封装迭代器块中的代码并实现可枚举接口,但也可以实现其他方法。An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. 如果可枚举的类由编译器生成,则该类将直接或间接嵌套在包含函数成员的类中,它将具有专用可访问性,并且它将具有为编译器使用 (标识符) 的保留名称。If an enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use (Identifiers).
可枚举对象可以实现比上面指定的接口更多的接口。An enumerable object may implement more interfaces than those specified above. 具体而言,可枚举对象还可以实现 IEnumerator
和 IEnumerator<T>
,使其既可作为可枚举的,也可用作枚举器。In particular, an enumerable object may also implement IEnumerator
and IEnumerator<T>
, enabling it to serve as both an enumerable and an enumerator. 在该类型的实现中,第一次调用可枚举对象的 GetEnumerator
方法时,将返回可枚举对象本身。In that type of implementation, the first time an enumerable object's GetEnumerator
method is invoked, the enumerable object itself is returned. 对可枚举对象的后续调用 GetEnumerator
(如果有)将返回可枚举对象的副本。Subsequent invocations of the enumerable object's GetEnumerator
, if any, return a copy of the enumerable object. 因此,每个返回的枚举器都有其自己的状态,并且一个枚举器中的更改不会影响另一个枚举Thus, each returned enumerator has its own state and changes in one enumerator will not affect another.
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
会转换为枚举器对象中的字段,因此它可以在的调用中继续存在 MoveNext
。Furthermore, the local variable i
is turned into a field in the enumerator object so it can continue to exist across invocations of MoveNext
.
下面的示例将打印整数1到10的简单乘法表。The following example prints a simple multiplication table of the integers 1 through 10. FromTo
该示例中的方法返回一个可枚举对象,并使用迭代器实现。The FromTo
method in the example returns an enumerable object and is implemented using an iterator.
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable<int> FromTo(int from, int to) {
while (from <= to) yield return from++;
}
static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
FromTo
方法可以转换为编译器生成的可枚举类的实例化,该枚举类封装迭代器块中的代码,如下所示。The FromTo
method can be translated into an instantiation of a compiler-generated enumerable class that encapsulates the code in the iterator block, as shown in the following.
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
class Test
{
...
static IEnumerable<int> FromTo(int from, int to) {
return new __Enumerable1(from, to);
}
class __Enumerable1:
IEnumerable<int>, IEnumerable,
IEnumerator<int>, IEnumerator
{
int __state;
int __current;
int __from;
int from;
int to;
int i;
public __Enumerable1(int __from, int to) {
this.__from = __from;
this.to = to;
}
public IEnumerator<int> GetEnumerator() {
__Enumerable1 result = this;
if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
result = new __Enumerable1(__from, to);
result.__state = 1;
}
result.from = result.__from;
return result;
}
IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)GetEnumerator();
}
public int Current {
get { return __current; }
}
object IEnumerator.Current {
get { return __current; }
}
public bool MoveNext() {
switch (__state) {
case 1:
if (from > to) goto case 2;
__current = from++;
__state = 1;
return true;
case 2:
__state = 2;
return false;
default:
throw new InvalidOperationException();
}
}
public void Dispose() {
__state = 2;
}
void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}
可枚举的类既实现可枚举接口又实现枚举器接口,使其既可用作可枚举的,也可用作枚举器。The enumerable class implements both the enumerable interfaces and the enumerator interfaces, enabling it to serve as both an enumerable and an enumerator. 第一次 GetEnumerator
调用方法时,将返回可枚举对象本身。The first time the GetEnumerator
method is invoked, the enumerable object itself is returned. 对可枚举对象的后续调用 GetEnumerator
(如果有)将返回可枚举对象的副本。Subsequent invocations of the enumerable object's GetEnumerator
, if any, return a copy of the enumerable object. 因此,每个返回的枚举器都有其自己的状态,并且一个枚举器中的更改不会影响另一个枚举Thus, each returned enumerator has its own state and changes in one enumerator will not affect another. Interlocked.CompareExchange
方法用于确保线程安全的操作。The Interlocked.CompareExchange
method is used to ensure thread-safe operation.
from
和 to
参数转换为可枚举类中的字段。The from
and to
parameters are turned into fields in the enumerable class. 因为 from
在迭代器块中修改了,所以引入了一个附加 __from
字段来保存 from
每个枚举器中给定的初始值。Because from
is modified in the iterator block, an additional __from
field is introduced to hold the initial value given to from
in each enumerator.
MoveNext
InvalidOperationException
如果在为时调用,则该方法将引发 __state
0
。The MoveNext
method throws an InvalidOperationException
if it is called when __state
is 0
. 这可以防止将可枚举对象用作枚举器对象,而无需先调用 GetEnumerator
。This protects against use of the enumerable object as an enumerator object without first calling GetEnumerator
.
下面的示例演示一个简单的 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. 请注意,不能用简单的语句编写翻译后的代码 foreach
。Note 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* 用于描述具有修饰符的任何类型的函数 async
。In general, the term _ async* is used to describe any kind of function that has the async
modifier.
异步函数的形参列表的编译时错误是指定任何 ref
或 out
参数。It is a compile-time error for the formal parameter list of an async function to specify any ref
or out
parameters.
异步方法的 return_type 必须是 void
或 任务类型。The return_type of an async method must be either void
or a task type. 任务类型是 System.Threading.Tasks.Task
和构造自的类型 System.Threading.Tasks.Task<T>
。The task types are System.Threading.Tasks.Task
and types constructed from System.Threading.Tasks.Task<T>
. 为了简单起见,在本章中,这些类型分别作为和引用 Task
Task<T>
。For the sake of brevity, in this chapter these types are referenced as Task
and Task<T>
, respectively. 返回任务类型的异步方法称作任务返回。An async method returning a task type is said to be task-returning.
任务类型的确切定义是定义的实现,但从语言的角度来看,任务类型处于 "未完成"、"成功" 或 "出错" 状态之一。The exact definition of the task types is implementation defined, but from the language's point of view a task type is in one of the states incomplete, succeeded or faulted. 出错的任务记录相关的异常。A faulted task records a pertinent exception. 已成功 Task<T>
记录类型的结果 T
。A succeeded Task<T>
records a result of type T
. 任务类型为可等待,因此可以是 await 表达式的操作数 (await 表达式) 。Task types are awaitable, and can therefore be the operands of await expressions (Await expressions).
异步函数调用能够通过等待表达式来挂起计算, (await 表达式 在其主体中) 。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.