类的定义和实例化
类和对象都能关联数据,将类想象成模具,将对象想象成根据模具浇筑出来的零件,可以更好理解这一点。
使用类,可以通过组合其他类型的变量、方法和事件创建自己的自定义类型。A class is a construct that enables you to create your own custom types by grouping together variables of other types, methods and events. 类好比是蓝图。A class is like a blueprint. 它定义类型的数据和行为。It defines the data and behavior of a type. 如果类未声明为静态,客户端代码就可以通过创建分配给变量的_对象_或_实例_来使用该类。If the class is not declared as static, client code can use it by creating objects or instances which are assigned to a variable. 变量会一直保留在内存中,直至对变量的所有引用超出范围为止。The variable remains in memory until all references to it go out of scope. 超出范围时,CLR 将对其进行标记,以便用于垃圾回收。At that time, the CLR marks it as eligible for garbage collection. 如果类声明为静态,则内存中只有一个副本,且客户端代码只能通过类本身,而不是实例变量来访问它。If the class is declared as static, then only one copy exists in memory and client code can only access it through the class itself, not an instance variable. 有关详细信息,请参阅静态类和静态类成员。For more information, see Static Classes and Static Class Members.
与结构不同,类支持_继承_,这是面向对象的编程的一个基本特点。Unlike structs, classes support inheritance, a fundamental characteristic of object-oriented programming. 有关详细信息,请参阅继承。
例子,包含类的定义、实例化、字段声明、字段访问和方法:
1 | using System; |
<>
this 关键字
this
关键字指代类的当前实例。在此示例中,this
用于限定类似名称隐藏的 Employee
类成员、name
和 alias
。In this example, this
is used to qualify the Employee
class members, name
and alias
, which are hidden by similar names. 它还用于将某个对象传递给属于其他类的方法 CalcTax
。
1 | using System; |
高级主题:存储和载入文件
将数据持久化存储到文件
示例:首先,实例化一个
FileStream
对象,将它与一个以员工的全名命名的文件对应起来。FileMode.Create
参数指明,如果对应的文件不存在就创建一个;如果文件存在,就覆盖它。接着创建一个StreamWriter
类。StreamWriter类负责将文本写入FileStream
。数据是用WriteLine()
方法写入的,就像向控制台写入一样。
1 | using System; |
写入操作完成后,FileStream
和StreamWriter
都需要关闭,避免它们在等待垃圾回收器运行期间,处于“不确定性打开”的状态。上述代码没有任何错误处理机制,如果引发异常,两个Close()
方法都不会执行。
从文件中获取数据
读取数据与存储数据相反,它使用StreamReader而不是StreamWriter。同样的,一旦数据读取完毕,就要在FileStream和StreamReader上调用Close()方法。
1 | using System; |
访问修饰符 Access Modifiers
访问修饰符是关键字,用于指定成员或类型已声明的可访问性。Access modifiers are keywords used to specify the declared accessibility of a member or a type. 本部分介绍四个访问修饰符:This section introduces the four access modifiers:
可以使用访问修饰符指定以下六个可访问性级别:The following six accessibility levels can be specified using the access modifiers:
public
:访问不受限制。public
: Access is not restricted.
protected
:访问限于包含类或派生自包含类的类型。protected
: Access is limited to the containing class or types derived from the containing class.
internal
:访问限于当前程序集。internal
: Access is limited to the current assembly.
protected internal
: 访问仅限于当前程序集或从包含类派生的类型。protected internal
: Access is limited to the current assembly or types derived from the containing class.
private
:访问限于包含类。private
: Access is limited to the containing type.
private protected
: 访问被限制为包含的类或从包含当前程序集中的类派生的类型。
示例:使用
private
访问修饰符。下例为了隐藏Password
字段,禁止从它包容类的外部访问,使用private
访问修饰符替代public
,这样就无法从Program
类中访问Password
字段了。
1 | public class Program { |
属性
属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。A property is a member that provides a flexible mechanism to read, write, or compute the value of a private field. 属性可用作公共数据成员,但它们实际上是称为访问器的特殊方法。Properties can be used as if they are public data members, but they are actually special methods called accessors. 这使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。
属性结合了字段和方法的多个方面。Properties combine aspects of both fields and methods. 对于对象的用户来说,属性似乎是一个字段,访问属性需要相同的语法。To the user of an object, a property appears to be a field, accessing the property requires the same syntax. 对于类的实现者来说,属性是一两个代码块,表示 get 访问器和/或 set 访问器。To the implementer of a class, a property is one or two code blocks, representing a get accessor and/or a set accessor. 读取属性时,执行 get
访问器的代码块;向属性赋予新值时,执行 set
访问器的代码块。The code block for the get
accessor is executed when the property is read; the code block for the set
accessor is executed when the property is assigned a new value. 将不带 set
访问器的属性视为只读。A property without a set
accessor is considered read-only. 将不带 get
访问器的属性视为只写。A property without a get
accessor is considered write-only. 将具有以上两个访问器的属性视为读写。A property that has both accessors is read-write.
与字段不同,属性不会被归类为变量。Unlike fields, properties are not classified as variables. 因此,不能将属性作为 ref 或 out 参数传递。
示例Example
此示例演示实例、静态和只读属性。This example demonstrates instance, static, and read-only properties. 它接收通过键盘键入的员工姓名,按 1 递增 NumberOfEmployees
,并显示员工姓名和编号。
1 | public class Employee { |
属性的声明
1 | using System; |
自动实现的属性
从C# 3.0开始,属性语法有了简化版本,允许在声明属性时,不添加取值或赋值方法,也不声明任何支持地段。一切都将自动实现。
1 | using System.IO; |
具有支持字段的属性 Properties with backing fields
有一个实现属性的基本模式,该模式使用私有支持字段来设置和检索属性值。One basic pattern for implementing a property involves using a private backing field for setting and retrieving the property value. get
访问器返回私有字段的值,set
访问器在向私有字段赋值之前可能会执行一些数据验证。The get
accessor returns the value of the private field, and the set
accessor may perform some data validation before assigning a value to the private field. 这两个访问器还可以在存储或返回数据之前对其执行某些转换或计算。Both accessors may also perform some conversion or computation on the data before it is stored or returned.
下面的示例阐释了此模式。The following example illustrates this pattern. 在此示例中,TimePeriod
类表示时间间隔。In this example, the TimePeriod
class represents an interval of time. 在内部,该类将时间间隔以秒为单位存储在名为 seconds
的私有字段中。Internally, the class stores the time interval in seconds in a private field named seconds
. 名为 Hours
的读-写属性允许客户以小时为单位指定时间间隔。A read-write property named Hours
allows the customer to specify the time interval in hours. get
和 set
访问器都会执行小时与秒之间的必要转换。Both the get
and the set
accessors perform the necessary conversion between hours and seconds. 此外,set
访问器还会验证数据,如果小时数无效,则引发 ArgumentOutOfRangeException。
1 | using System; |
表达式主体定义 Expression body definitions
属性访问器通常由单行语句组成,这些语句只分配或只返回表达式的结果。Property accessors often consist of single-line statements that just assign or return the result of an expression. 可以将这些属性作为 expression-bodied 成员来实现。You can implement these properties as expression-bodied members. =>
符号后跟用于为属性赋值或从属性中检索值的表达式,即组成了表达式主体定义。Expression body definitions consist of the =>
symbol followed by the expression to assign to or retrieve from the property.
从 C# 6 开始,只读属性可以将 get
访问器作为 expression-bodied 成员实现。Starting with C# 6, read-only properties can implement the get
accessor as an expression-bodied member. 在这种情况下,既不使用 get
访问器关键字,也不使用 return
关键字。In this case, neither the get
accessor keyword nor the return
keyword is used. 下面的示例将只读 Name
属性作为 expression-bodied 成员实现。The following example implements the read-only Name
property as an expression-bodied member.
1 | using System; |
从 C# 7 开始,get
和 set
访问器都可以作为 expression-bodied 成员实现。Starting with C# 7, both the get
and the set
accessor can be implemented as expression-bodied members. 在这种情况下,必须使用 get
和 set
关键字。In this case, the get
and set
keywords must be present. 下面的示例阐释如何为这两个访问器使用表达式主体定义。The following example illustrates the use of expression body definitions for both accessors. 请注意,return
关键字不与 get
访问器搭配使用。Note that the return
keyword is not used with the get
accessor.
1 | using System; |
只读和只写属性
省略 set
访问器可使属性为只读,省略 get
访问器可使属性为只写。只读属性对于任何赋值气度都会造成编译错误。例如,下例中是Id
为只读:
1 | class Program { |
上例中采用Employee构造函数(而不是属性)对字段进行赋值(_Id = id)。如果通过属性来赋值,会造成编译错误。
限制访问器可访问性(C# 编程指南)Restricting Accessor Accessibility (C# Programming Guide)
属性或索引器的 get 和 set 部分称为访问器。The get and set portions of a property or indexer are called accessors. 默认情况下,这些访问器具有相同的可见性或访问级别:其所属属性或索引器的可见性或访问级别。By default these accessors have the same visibility, or access level: that of the property or indexer to which they belong. 有关详细信息,请参阅可访问性级别。For more information, see accessibility levels. 不过,有时限制对其中某个访问器的访问是有益的。However, it is sometimes useful to restrict access to one of these accessors. 通常是在保持 get
访问器可公开访问的情况下,限制 set
访问器的可访问性。Typically, this involves restricting the accessibility of the set
accessor, while keeping the get
accessor publicly accessible.
可以为get或set部分指定访问修饰符(但不能为两者都指定),从而覆盖为属性声明指定的访问修饰符。例如:
1 | class Program { |
为赋值方法指定private修饰符后,属性对于处Employee之外的其他类来说是只读的。在Employee类内部,属性是可读/可写的,所以可在构造器中对属性进行赋值。为取值方法或赋值方法指定访问修饰符时,注意该访问修饰符的“限制性”必须比应用于整个属相的访问修饰符更“严格”。例如,属性声明为较为严格的private,但将它的赋值方法声明为较宽松的public,就会发生编译错误。
属性作为虚字段使用
下例中,Name属性的实现:
1 | using System; |
构造器 Constructors (构造函数)
每当创建类或结构时,将会调用其构造函数。Whenever a class or struct is created, its constructor is called. 类或结构可能具有采用不同参数的多个构造函数。A class or struct may have multiple constructors that take different arguments. 使用构造函数,程序员能够设置默认值、限制实例化,并编写灵活易读的代码。Constructors enable the programmer to set default values, limit instantiation, and write code that is flexible and easy to read. 有关详细信息和示例,请参阅使用构造函数和实例构造函数。
构造函数声明与调用
1 | class Employee |
构造函数是一种方法
,其名称与其类名`
完全相同。 其方法签名仅包含方法名称和其参数列表;它
没有返回类型`。构造函数是“运行时”用来初始化对象实例的方法。在此例中,构造函数以员工的名字和姓氏作为参数,允许程序员在实例化Employee对象时制定这些参数的值。如下例:
1 | public class Program |
如果某个构造函数可以作为单个语句实现,则可以使用表达式主体定义。If a constructor can be implemented as a single statement, you can use an expression body definition. 以下示例定义 Location
类,其构造函数具有一个名为“name”的字符串参数。The following example defines a Location
class whose constructor has a single string parameter named name. 表达式主体定义给 locationName
字段分配参数。The expression body definition assigns the argument to the locationName
field.
1 | public class Location |
默认构造器 Default constructors
如果没有为类提供构造函数,默认情况下,C# 将创建一个会实例化对象并将成员变量设置为默认值的构造函数,如默认值表中所列。If you don’t provide a constructor for your class, C# creates one by default that instantiates the object and sets member variables to the default values as listed in the Default Values Table. 如果没有为结构提供构造函数,C# 将依赖于隐式默认构造函数,自动将值类型的每个字段初始化为其默认值,如默认值表中所列。If you don’t provide a constructor for your struct, C# relies on an implicit default constructor to automatically initialize each field of a value type to its default value as listed in the Default Values Table. 有关详细信息和示例,请参阅实例构造函数。
对象初始化器 Object Initializer
可以使用对象初始值设定项
(对象初始化器)以声明方式初始化类型对象,而无需显式调用类型的构造函数。You can use object initializers to initialize type objects in a declarative manner without explicitly invoking a constructor for the type.
以下示例演示如何将对象初始化器
用于命名对象。The following examples show how to use object initializers with named objects. 编译器通过首先访问默认实例构造函数,然后处理成员初始化来处理对象初始值设定项。The compiler processes object initializers by first accessing the default instance constructor and then processing the member initializations. 因此,如果默认构造函数在类中声明为 private
,则需要公共访问的对象初始值设定项将失败。Therefore, if the default constructor is declared as private
in the class, object initializers that require public access will fail.
如果要定义匿名类型,则必须使用对象初始化器
。You must use an object initializer if you’re defining an anonymous type. 有关详细信息,请参阅如何:在查询中返回元素属性的子集。For more information, see How to: Return Subsets of Element Properties in a Query.
下面的示例演示如何使用对象初始化器
初始化新的 StudentName
类型。
1 | public class Program { |
下面的示例演示如何使用集合初始化器
来初始化 StudentName
类型的集合。The following example shows how to initialize a collection of StudentName
types by using a collection initializer. 请注意,集合初始值设定项是一系列由逗号分隔的对象初始值设定项。Note that a collection initializer is a series of comma-separated object initializers.
1 | List<StudentName> students = new List<StudentName>() |
终结器 Finalizers
终结器用于析构类的实例。Finalizers are used to destruct instances of classes.
备注 Remarks
无法在结构中定义终结器。Finalizers cannot be defined in structs. 它们仅用于类。They are only used with classes.
一个类只能有一个终结器。A class can only have one finalizer.
不能继承或重载终结器。Finalizers cannot be inherited or overloaded.
不能手动调用终结器。Finalizers cannot be called. 可以自动调用它们。They are invoked automatically.
终结器不使用修饰符或参数。
例如,以下是类 Car
的终结器声明。For example, the following is a declaration of a finalizer for the Car
class.
1 | class Car |
终结器也可以作为表达式主体定义实现,如下面的示例所示。A finalizer can also be implemented as an expression body definition, as the following example shows.
1 | using System; |
程序员无法控制何时调用终结器,因为这由垃圾回收器决定。The programmer has no control over when the finalizer is called because this is determined by the garbage collector. 垃圾回收器检查应用程序不再使用的对象。The garbage collector checks for objects that are no longer being used by the application. 如果它认为某个对象符合终止条件,则调用终结器(如果有),并回收用来存储此对象的内存。If it considers an object eligible for finalization, it calls the finalizer (if any) and reclaims the memory used to store the object. 还可在程序退出后调用终结器。Finalizers are also called when the program exits.
可以通过调用 Collect 强制进行垃圾回收,但多数情况下应避免此操作,因为它可能会造成性能问题。
构造器的重载
1 | class Employee { |
应优先使用可选参数而不是重载,以便在API中清楚地看出“默认”属性的默认值。例如,Person的一个构造器签名Person(string firstName, string lastName, int? age = null)就清楚地指明如果Person的Age未指定,就将它默认为null。
构造器链:使用this调用另一个构造器
上例中,对Employee对象进行初始化的代码多处重复,可以从一个构造器中调用另一个构造器,避免重复输入代码。这称为构造器链,它是用构造器初始化器来实现的。C#采用的语法格式是在一个冒号后面添加this关键字,再添加被调用构造器的参数列表。构造器初始化器在自行当前的构造器实现之前,判断要调用另外哪一个构造器,实例如下:
1 | class Employee { |
上例中,3个参数的构造器调用2个参数的构造器。通常情况下,用参数最少
的构造器调用参数最多
的构造器,为未知的参数传递默认值。如下例:
1 | public Employee(int id):this(id,"","") |
构造器链
完整例子参考:
1 | using System; |
初学者主题:集中初始化
创建单独的方法,将所有初始化代码集中在一起,如下例中,创建名为Initialize()方法,它同时获取员工的名字、姓氏和ID。示例如下:
1 | class Employee { |
匿名类型
C# 3.0引入了对匿名类型的支持,匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。
1 | using System; |
匿名类型通常用在查询表达式的 select 子句中,以便返回源序列中每个对象的属性子集。Anonymous types typically are used in the select clause of a query expression to return a subset of the properties from each object in the source sequence. 有关查询的详细信息,请参阅 LINQ 查询表达式。
静态成员
使用 static
修饰符可声明属于类型本身而不是属于特定对象的静态成员。Use the static
modifier to declare a static member, which belongs to the type itself rather than to a specific object. static
修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、终结器或类以外的类型。The static
modifier can be used with classes, fields, methods, properties, operators, events, and constructors, but it cannot be used with indexers, finalizers, or types other than classes. 有关详细信息,请参阅静态类和静态类成员。For more information, see Static Classes and Static Class Members.
C# 不支持静态局部变量(在方法范围中声明的变量)。C# does not support static local variables (variables that are declared in method scope).
可在成员的返回类型之前使用 static
关键字声明静态类成员,如下面的示例所示:You declare static class members by using the static
keyword before the return type of the member, as shown in the following example:
1 | public class Automobile |
在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。Static members are initialized before the static member is accessed for the first time and before the static constructor, if there is one, is called. 若要访问静态类成员,请使用类的名称(而不是变量名称)指定成员的位置,如下面的示例所示:To access a static class member, use the name of the class instead of a variable name to specify the location of the member, as shown in the following example:
1 | Automobile.Drive(); |
如果类包含静态字段,则提供在类加载时初始化它们的静态构造函数。
常量或类型声明是隐式的静态成员。A constant or type declaration is implicitly a static member.
不能通过实例引用静态成员。A static member cannot be referenced through an instance. 然而,可以通过类型名称引用它。Instead, it is referenced through the type name. 例如,请考虑以下类:For example, consider the following class:
1 | public class MyBaseC |
若要引用静态成员 x
,除非可从相同范围访问该成员,否则请使用完全限定的名称 MyBaseC.MyStruct.x
:To refer to the static member x
, use the fully qualified name, MyBaseC.MyStruct.x
, unless the member is accessible from the same scope:
1 | Console.WriteLine(MyBaseC.MyStruct.x); |
尽管类的实例包含该类的所有实例字段的单独副本,但每个静态
字段只有一个副本
。
示例
此示例显示,尽管可以使用尚未声明的其他静态字段来初始化某个静态字段,但除非向该静态字段显式分配值,否则不会定义该结果。This example shows that although you can initialize a static field by using another static field not yet declared, the results will be undefined until you explicitly assign a value to the static field.
1 | using System; |
静态字段
使用
static
关键字声明一个静态字段
,然后访问:
1 | using System; |
NextId
字段声明包含static
修饰符,成为静态字段。静态字段
和实例字段
一样可以在声明时进行初始化。静态字段
只属于类本身,从类外部访问静态字段时要使用类名
,而不是类实例(变量)名。只有在类(或者派生类)内部代码中,才可以省略类名。
静态方法
Console.WriteLine()这些,就是静态方法。无需实例话,直接引用。
静态构造器
C#支持静态构造器
,不允许有任何参数。使用静态构造器将类中的静态数据初始化成特定的值,尤其是无法通过声明时的一次简单赋值来获得初始值的时候。
声明静态构造器
1 | using System; |
在静态构造器中进行的赋值,优先于声明时的赋值,这和实例字段情况一样。不要在静态构造器中抛出异常,这会造成类型在用用程序的剩余生存期内无法使用。
静态属性
可以将属性声明为static,使用静态属性几乎肯定比使用公共静态字段好,因为公共静态字段在任何地方都能调用,而静态属性至少提供了一定程度的封装。
1 | class Employee { |
静态类
静态类无法实例化。 换句话说,无法使用 new
关键字创建类类型的变量。 由于不存在任何实例变量,因此可以使用类名本身
访问静态类的成员。静态类可以用作只对输入参数进行操作并且不必获取或设置任何内部实例字段的方法集的方便容器。
以下列表提供静态类的主要功能:The following list provides the main features of a static class:
只包含静态成员。Contains only static members.
无法进行实例化。Cannot be instantiated.
会进行密封。Is sealed.
不能包含实例构造函数。
因此,创建静态类基本上与创建只包含静态成员和私有构造函数的类相同。Creating a static class is therefore basically the same as creating a class that contains only static members and a private constructor. 私有构造函数可防止类进行实例化。A private constructor prevents the class from being instantiated. 使用静态类的优点是编译器可以进行检查,以确保不会意外地添加任何实例成员。The advantage of using a static class is that the compiler can check to make sure that no instance members are accidentally added. 编译器可保证无法创建此类的实例。The compiler will guarantee that instances of this class cannot be created.
静态类会进行密封,因此不能继承。Static classes are sealed and therefore cannot be inherited. 它们不能继承自任何类(除了 Object)。They cannot inherit from any class except Object. 静态类不能包含实例构造函数;但是,它们可以包含静态构造函数。Static classes cannot contain an instance constructor; however, they can contain a static constructor. 如果类包含需要进行重要初始化的静态成员,则非静态类还应定义静态构造函数。Non-static classes should also define a static constructor if the class contains static members that require non-trivial initialization. 有关详细信息,请参阅静态构造函数。
1 | using System; |
扩展方法
转自 田小计划
当我们想为一个现有的类型添加一个方法的时候,有两种方式:一是直接在现有类型中添加方法;但是很多情况下现有类型都是不允许修改的,那么可以使用第二种方式,基于现有类型创建一个子类,然后在子类中添加想要的方法。
当C# 2.0中出现了静态类之后,对于上面的问题,我们也可以创建静态工具类来实现想要添加的方法。这样做可以避免创建子类,但是在使用时代码就没有那么直观了。
其实,上面的方法都不是很好的解决办法。在C# 3.0中出现了扩展方法,通过扩展方法我们可以直接在一个现有的类型上”添加”方法。当使用扩展方法的时候,可以像调用实例方法一样的方式来调用扩展方法。
扩展方法的声明和调用
相比普通方法,扩展方法有它自己的特征,下面就来看看怎么声明一个扩展方法:
- 它必须在一个非嵌套、非泛型的静态类中(所以扩展方法一定是静态方法)
- 它至少要有一个参数
- 第一个参数必须加上this关键字作为前缀
- 第一个参数类型也称为扩展类型(extended type),表示该方法对这个类型进行扩展
- 第一个参数不能用其他任何修饰符(比如out或ref)
- 第一个参数的类型不能是指针类型
根据上面的要求,我们给int类型添加了一个扩展方法,用来判断一个int值是不是偶数:
1 | using System; |
通过上面的例子可以看到,当调用扩展方法的时候,可以像调用实例方法一样。这就是我们使用扩展方法的原因之一,我们可以给一个已有类型”添加”一个方法。
既然扩展方法是一个静态类的方法,我们当然也可以通过静态类来调用这个方法。
通过IL可以看到,其实扩展方法也是编译器为我们做了一些转换,将扩展方法转化成静态类的静态方法调用。
1 | .method private hidebysig static |
const 和 readonly
- const修饰的常量在声明时必须初始化值;readonly修饰的常量可以不初始化值,且可以延迟到构造函数。
- cons修饰的常量在编译期间会被解析,并将常量的值替换成初始化的值;而readonly延迟到运行的时候。
- const修饰的常量注重的是效率;readonly修饰的常量注重灵活。
- const修饰的常量没有内存消耗;readonly因为需要保存常量,所以有内存消耗。
- const只能修饰基元类型、枚举类、或者字符串类型;readonly却没有这个限制。
const
常量是不可变的值,在编译时是已知的,在程序的生命周期内不会改变。Constants are immutable values which are known at compile time and do not change for the life of the program. 常量使用 const 修饰符声明。Constants are declared with the const modifier. 仅 C# 内置类型(不包括 System.Object)可声明为 const
。Only the C# built-in types (excluding System.Object) may be declared as const
. 有关内置类型的列表,请参阅内置类型表。For a list of the built-in types, see Built-In Types Table. 用户定义的类型(包括类、结构和数组)不能为 const
。User-defined types, including classes, structs, and arrays, cannot be const
. 使用 readonly 修饰符创建在运行时一次性(例如在构造函数中)初始化的类、结构或数组,此后不能更改。Use the readonly modifier to create a class, struct, or array that is initialized one time at runtime (for example in a constructor) and thereafter cannot be changed.
C# 不支持 const
方法、属性或事件。C# does not support const
methods, properties, or events.
枚举类型使你能够为整数内置类型定义命名常量(例如 int
、uint
、long
等)。The enum type enables you to define named constants for integral built-in types (for example int
, uint
, long
, and so on). 有关详细信息,请参阅枚举。For more information, see enum.
常量在声明时必须初始化。Constants must be initialized as they are declared. 例如: For example:
1 | class Calendar1 |
可以同时声明多个同一类型的常量,例如:Multiple constants of the same type can be declared at the same time, for example:
1 | class Calendar2 |
常量字段自动成为静态字段,将常量字段显式声明为static会造成编译错误。
readonly
readonly
关键字是一个可在字段上使用的修饰符。The readonly
keyword is a modifier that you can use on fields. 当字段声明包括 readonly
修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中。When a field declaration includes a readonly
modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.
在此示例中,即使在类构造函数中给字段 year
赋了值,它的值仍无法在 ChangeYear
方法中更改:In this example, the value of the field year
cannot be changed in the method ChangeYear
, even though it is assigned a value in the class constructor:
1 | class Age |
readonly
关键字不同于 const 关键字。const
字段只能在该字段的声明中初始化。
readonly
字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly
字段可能具有不同的值。另外,虽然 const
字段是编译时常量,但 readonly
字段可用于运行时常量,如下面的示例所示:
1 | public static readonly uint timeStamp = (uint)DateTime.Now.Ticks; |
示例
1 | using System; |
在前面的示例中,如果使用如下的语句:
p2.y = 66; // Error
将收到编译器错误消息:you will get the compiler error message:
The left-hand side of an assignment must be an l-value
这与尝试给常数赋值时收到的错误相同。
声明不包含字面值的类型的readonly字段
1 | using System; |
嵌套类
在类或构造中定义的类型称为嵌套类型。A type defined within a class or struct is called a nested type. 例如:
1 | class Container |
不论外部类型是类还是构造,嵌套类型均默认
为 private;仅可从其包含类型中进行访问。Regardless of whether the outer type is a class or a struct, nested types default to private; they are accessible only from their containing type. 在上一个示例中,Nested
类无法访问外部类型。
还可指定访问修饰符来定义嵌套类型的可访问性,如下所示:You can also specify an access modifier to define the accessibility of a nested type, as follows:
嵌套类型的类可以是公共,保护,内部,受保护内部,私有或私有受保护。Nested types of a class can be public, protected, internal, protected internal, private or private protected.
但是,定义
protected
,protected internal
或private protected
嵌套类内的密封类将生成编译器警告CS0628,”新的保护的成员声明为密封类中。”However, defining aprotected
,protected internal
orprivate protected
nested class inside a sealed class generates compiler warning CS0628, “new protected member declared in sealed class.”构造的嵌套类型可以是 public、internal 或 private。Nested types of a struct can be public, internal, or private.
以下示例使 Nested
类为 public:The following example makes the Nested
class public:
1 | class Container |
分部类和方法 Partial Classes and Methods
可以将类或结构、接口或方法的定义拆分到两个或更多个源文件中。It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组合起来。Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.
分部类 Partial Classes
在以下几种情况下需要拆分类定义:There are several situations when splitting a class definition is desirable:
处理大型项目时,使一个类分布于多个独立文件中可以让多位程序员同时对该类进行处理。When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time.
使用自动生成的源时,无需重新创建源文件便可将代码添加到类中。When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio 在创建 Windows 窗体、Web 服务包装器代码等时都使用此方法。Visual Studio uses this approach when it creates Windows Forms, Web service wrapper code, and so on. 无需修改 Visual Studio 创建的文件,就可创建使用这些类的代码。You can create code that uses these classes without having to modify the file created by Visual Studio.
若要拆分类定义,请使用 partial 关键字修饰符,如下所示:To split a class definition, use the partial keyword modifier, as shown here:
1 | // File:Program1.cs |
1 | // File:Program2.cs |
partial
关键字指示可在命名空间中定义该类、结构或接口的其他部分。The partial
keyword indicates that other parts of the class, struct, or interface can be defined in the namespace. 所有部分都必须使用 partial
关键字。All the parts must use the partial
keyword. 在编译时,各个部分都必须可用来形成最终的类型。All the parts must be available at compile time to form the final type. 各个部分必须具有相同的可访问性,如 public
、private
等。
嵌套类型是可以分部的:
1 | // File: Program.cs |
1 | // File: Program+CommandLine.cs |
分部方法 Partial Methods
C# 3.0 引入了分部方法概念,对 C# 2.0 的分部类进行了扩展。分部方法只存在于分部类中。
分部类或结构可以包含分部方法。A partial class or struct may contain a partial method. 类的一个部分包含方法的签名。One part of the class contains the signature of the method. 可以在同一部分或另一个部分中定义可选实现。An optional implementation may be defined in the same part or another part. 如果未提供该实现,则会在编译时删除方法以及对方法的所有调用。If the implementation is not supplied, then the method and all calls to the method are removed at compile time.
分部方法使类的某个部分的实施者能够定义方法(类似于事件)。Partial methods enable the implementer of one part of a class to define a method, similar to an event. 类的另一部分的实施者可以决定是否实现该方法。The implementer of the other part of the class can decide whether to implement the method or not. 如果未实现该方法,编译器会删除方法签名以及对该方法的所有调用。If the method is not implemented, then the compiler removes the method signature and all calls to the method. 调用该方法(包括调用中的任何参数计算结果)在运行时没有任何影响。The calls to the method, including any results that would occur from evaluation of arguments in the calls, have no effect at run time. 因此,分部类中的任何代码都可以随意地使用分部方法,即使未提供实现也是如此。Therefore, any code in the partial class can freely use a partial method, even if the implementation is not supplied. 调用但不实现该方法不会导致编译时错误或运行时错误。No compile-time or run-time errors will result if the method is called but not implemented.
在自定义生成的代码时,分部方法特别有用。Partial methods are especially useful as a way to customize generated code. 这些方法允许保留方法名称和签名,因此生成的代码可以调用方法,而开发人员可以决定是否实现方法。They allow for a method name and signature to be reserved, so that generated code can call the method but the developer can decide whether to implement the method. 与分部类非常类似,分部方法使代码生成器创建的代码和开发人员创建的代码能够协同工作,而不会产生运行时开销。Much like partial classes, partial methods enable code created by a code generator and code created by a human developer to work together without run-time costs.
分部方法声明由两个部分组成:定义和实现。A partial method declaration consists of two parts: the definition, and the implementation. 它们可以位于分部类的不同部分中,也可以位于同一部分中。These may be in separate parts of a partial class, or in the same part. 如果不存在实现声明,则编译器会优化定义声明和对方法的所有调用。If there is no implementation declaration, then the compiler optimizes away both the defining declaration and all calls to the method.
1 | // Definition in file1.cs |
1 | // Implementation in file2.cs |
分部方法声明必须以上下文关键字 partial 开头,并且方法必须返回 void。Partial method declarations must begin with the contextual keyword partial and the method must return void.
分部方法可以有 ref 参数,但不能有 out 参数。Partial methods can have ref but not out parameters.
分部方法为隐式 private 方法,因此不能为 virtual 方法。Partial methods are implicitly private, and therefore they cannot be virtual.
分部方法不能为 extern 方法,因为主体的存在确定了方法是在定义还是在实现。Partial methods cannot be extern, because the presence of the body determines whether they are defining or implementing.
分部方法可以有 static 和 unsafe 修饰符。Partial methods can have static and unsafe modifiers.
分部方法可以是泛型的。Partial methods can be generic. 约束将放在定义分部方法声明上,但也可以选择重复放在实现声明上。Constraints are put on the defining partial method declaration, and may optionally be repeated on the implementing one. 参数和类型参数名称在实现声明和定义声明中不必相同。Parameter and type parameter names do not have to be the same in the implementing declaration as in the defining one.
你可以为已定义并实现的分部方法生成委托,但不能为已经定义但未实现的分部方法生成委托。