C#本质论笔记 第5章 类

类的定义和实例化

类和对象都能关联数据,将类想象成模具,将对象想象成根据模具浇筑出来的零件,可以更好理解这一点。

使用类,可以通过组合其他类型的变量、方法和事件创建自己的自定义类型。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

public class Program {
public static void Main () {
Employee employee1 = new Employee ();
Employee employee2;
employee2 = new Employee ();

employee1.FirstName = "Inigo";
employee1.LastName = "Montoya";
employee1.Salary = "Too Little";
IncreaseSalary (employee1);
Console.WriteLine ("{0}: {1}", employee1.GetName (), employee1.Salary);
}

static void IncreaseSalary (Employee employee) {
employee.Salary = "Enough to survive on";
}
}

class Employee {
public string FirstName;
public string LastName;
public string Salary = "Not enough";

public string GetName () {
return FirstName + " " + LastName;
}
}

<>

this 关键字

this 关键字指代类的当前实例。在此示例中,this 用于限定类似名称隐藏的 Employee 类成员、namealias。In this example, this is used to qualify the Employee class members, name and alias, which are hidden by similar names. 它还用于将某个对象传递给属于其他类的方法 CalcTax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;
class Employee {
private string name;
private string alias;
private decimal salary = 3000.00m;

// Constructor:
public Employee (string name, string alias) {
// Use this to qualify the fields, name and alias:
// 如果不用 this 指定类成员 name,那么 name = name; 只是自己给自己赋值,
// 最终,类成员 name 未被赋值,为 null
this.name = name;
this.alias = alias;
}
// Printing method:
public void printEmployee () {
Console.WriteLine ("Name: {0}\nAlias: {1}", name, alias);
// Passing the object to the CalcTax method by using this:
Console.WriteLine ("Taxes: {0:C}", Tax.CalcTax (this));
}

public decimal Salary {
get { return salary; }
}
}

class Tax {
public static decimal CalcTax (Employee E) {
return 0.08m * E.Salary;
}
}

class MainClass {
static void Main () {
// Create objects:
Employee E1 = new Employee ("Mingda Pan", "mpan");

// Display results:
E1.printEmployee ();
}
}
/*
Output:
Name: Mingda Pan
Alias: mpan
Taxes: $240.00
*/

高级主题:存储和载入文件

将数据持久化存储到文件

示例:首先,实例化一个FileStream对象,将它与一个以员工的全名命名的文件对应起来。FileMode.Create参数指明,如果对应的文件不存在就创建一个;如果文件存在,就覆盖它。接着创建一个StreamWriter类。StreamWriter类负责将文本写入FileStream。数据是用WriteLine()方法写入的,就像向控制台写入一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
// IO namespace
using System.IO;
class Employee {
public string FirstName;
public string LastName;
public string Salary;

public void Save () {
DataStorage.Store (this);
}
}

class DataStorage {
// Save an employee object to a file
// named with the Employee name.
// Error handling not shown.
public static void Store (Employee employee) {
// Instantiate a FileStream using FirstNameLastName.dat
// for the filename. FileMode.Create will force
// a new file to be created or override an
// existing file.
FileStream stream = new FileStream (
employee.FirstName + employee.LastName + ".dat",
FileMode.Create);

// Create a StreamWriter object for writing text
// into the FileStream
StreamWriter writer = new StreamWriter (stream);

// Write all the data associated with the employee.
writer.WriteLine (employee.FirstName);
writer.WriteLine (employee.LastName);
writer.WriteLine (employee.Salary);

// Close the StreamWriter and its Stream.
writer.Close (); // Automatically closes the stream
stream.Close();
}
}

class SaveDataToFile {
static void Main (string[] args) {
Employee employee = new Employee ();
employee.FirstName = "Jon";
employee.LastName = "Snow";
employee.Salary = "$6000.00";
employee.Save ();
}
}

写入操作完成后,FileStreamStreamWriter都需要关闭,避免它们在等待垃圾回收器运行期间,处于“不确定性打开”的状态。上述代码没有任何错误处理机制,如果引发异常,两个Close()方法都不会执行。

从文件中获取数据

读取数据与存储数据相反,它使用StreamReader而不是StreamWriter。同样的,一旦数据读取完毕,就要在FileStream和StreamReader上调用Close()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.IO;

public class Program {
public static void Main () {
Employee employee1;

Employee employee2 = new Employee ();
employee2.SetName ("Inigo", "Montoya");
employee2.Save ();

// Modify employee2 after saving.
IncreaseSalary (employee2);

// Load employee1 from the saved version of employee2
employee1 = DataStorage.Load ("Inigo", "Montoya");

Console.WriteLine (
"{0}: {1}",
employee1.GetName (),
employee1.Salary);
}

static void IncreaseSalary (Employee employee) {
employee.Salary = "Enough to survive on";
}
}

class Employee {
public string FirstName;
public string LastName;
public string Salary;

public string GetName () {
return FirstName + " " + LastName;
}

public void SetName (string newFirstName, string newLastName) {
this.FirstName = newFirstName;
this.LastName = newLastName;
Console.WriteLine ("Name changed to '{0}'",
this.GetName ());
}

public void Save () {
DataStorage.Store (this);
}
}

class DataStorage {
// Save an employee object to a file
// named with the Employee name.
// Error handling not shown.
public static void Store (Employee employee) {
// Instantiate a FileStream using FirstNameLastName.dat
// for the filename. FileMode.Create will force
// a new file to be created or override an
// existing file.
FileStream stream = new FileStream (
employee.FirstName + employee.LastName + ".dat",
FileMode.Create);

// Create a StreamWriter object for writing text
// into the FileStream
StreamWriter writer = new StreamWriter (stream);

// Write all the data associated with the employee.
writer.WriteLine (employee.FirstName);
writer.WriteLine (employee.LastName);
writer.WriteLine (employee.Salary);

// Close the StreamWriter and its Stream.
writer.Close (); // Automatically closes the stream
stream.Close ();
}

public static Employee Load (string firstName, string lastName) {
Employee employee = new Employee ();

// Instantiate a FileStream using FirstNameLastName.dat
// for the filename. FileMode.Open will open
// an existing file or else report an error.
FileStream stream = new FileStream (
firstName + lastName + ".dat", FileMode.Open);

// Create a SteamReader for reading text from the file.
StreamReader reader = new StreamReader (stream);

// Read each line from the file and place it into
// the associated property.
employee.FirstName = reader.ReadLine ();
employee.LastName = reader.ReadLine ();
employee.Salary = reader.ReadLine ();

// Close the StreamReader and its Stream.
reader.Close (); // Automatically closes the stream
stream.Close ();

return employee;
}
}

访问修饰符 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Program {
public static void Main () {
Employee employee = new Employee ();

employee.FirstName = "Inigo";
employee.LastName = "Montoya";

// Password is private, so it cannot be
// accessed from outside the class.
// Console.WriteLine(
// ("Password = {0}", employee.Password);
}
// ...
}

class Employee {
public string FirstName;
public string LastName;
public string Salary;
private string Password;
private bool IsAuthenticated;

public bool Logon (string password) {
if (Password == password) {
IsAuthenticated = true;
}
return IsAuthenticated;
}

public bool GetIsAuthenticated () {
return IsAuthenticated;
}
// ...
}

属性

属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。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. 因此,不能将属性作为 refout 参数传递。

示例Example

此示例演示实例、静态和只读属性。This example demonstrates instance, static, and read-only properties. 它接收通过键盘键入的员工姓名,按 1 递增 NumberOfEmployees,并显示员工姓名和编号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Employee {
public static int NumberOfEmployees;
private static int counter;
private string name;

// A read-write instance property:
public string Name {
get { return name; }
set { name = value; }
}

// A read-only static property:
public static int Counter {
get { return counter; }
}

// A Constructor:
public Employee () {
// Calculate the employee's number:
counter = ++counter + NumberOfEmployees;
}
}

class TestEmployee {
static void Main () {
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee ();
e1.Name = "Claude Vige";

System.Console.WriteLine ("Employee number: {0}", Employee.Counter);
System.Console.WriteLine ("Employee name: {0}", e1.Name);
}
}
/* Output:
Employee number: 108
Employee name: Claude Vige
*/

属性的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.IO;

public class Program {
public static void Main () {
Employee employee = new Employee ();

// Call the FirstName property's setter.
employee.FirstName = "Inigo";

// Call the FirstName property's getter.
System.Console.WriteLine (employee.FirstName);
}
}

class Employee {
// FirstName property
public string FirstName {
get {
return _FirstName;
}
set {
_FirstName = value;
}
}
private string _FirstName;

// LastName property
public string LastName {
get {
return _LastName;
}
set {
_LastName = value;
}
}
private string _LastName;
}

自动实现的属性

从C# 3.0开始,属性语法有了简化版本,允许在声明属性时,不添加取值或赋值方法,也不声明任何支持地段。一切都将自动实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System.IO;
using System;

public class Program {
public static void Main () {
Employee employee1 =
new Employee ();
Employee employee2 =
new Employee ();

// Call the FirstName property's setter.
employee1.FirstName = "Inigo";

// Call the FirstName property's getter.
System.Console.WriteLine (employee1.FirstName);

// Assign an auto-implemented property
employee2.Title = "Computer Nerd";
employee1.Manager = employee2;

// Print employee1's manager's title.
System.Console.WriteLine (employee1.Manager.Title);
}
}

class Employee {
// FirstName property
public string FirstName {
get {
return _FirstName;
}
set {
_FirstName = value;
}
}
private string _FirstName;

// LastName property
public string LastName {
get {
return _LastName;
}
set {
_LastName = value;
}
}
private string _LastName;

// Title property
public string Title { get; set; }

// Manager property
public Employee Manager { get; set; }
}

具有支持字段的属性 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. getset 访问器都会执行小时与秒之间的必要转换。Both the get and the set accessors perform the necessary conversion between hours and seconds. 此外,set 访问器还会验证数据,如果小时数无效,则引发 ArgumentOutOfRangeException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

class TimePeriod {
private double seconds;

public double Hours {
get { return seconds / 3600; }
set {
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException (
$"{nameof(value)} must be between 0 and 24.");

seconds = value * 3600;
}
}
}

class Program {
static void Main () {
TimePeriod t = new TimePeriod ();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;

// Retrieving the property causes the 'get' accessor to be called.
Console.WriteLine ($"Time in hours: {t.Hours}");
}
}
// The example displays the following output:
// Time in hours: 24

表达式主体定义 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

public class Person {
private string firstName;
private string lastName;

public Person (string first, string last) {
firstName = first;
lastName = last;
}

public string Name => $"{firstName} {lastName}";
}

public class Example {
public static void Main () {
var person = new Person ("Isabelle", "Butts");
Console.WriteLine (person.Name);
}
}
// The example displays the following output:
// Isabelle Butts

从 C# 7 开始,getset 访问器都可以作为 expression-bodied 成员实现。Starting with C# 7, both the get and the set accessor can be implemented as expression-bodied members. 在这种情况下,必须使用 getset 关键字。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;

public class SaleItem {
string name;
decimal cost;

public SaleItem (string name, decimal cost) {
this.name = name;
this.cost = cost;
}

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

public decimal Price {
get => cost;
set => cost = value;
}
}

class Program {
static void Main (string[] args) {
var item = new SaleItem ("Shoes", 19.95m);
Console.WriteLine ($"{item.Name}: sells for {item.Price:C2}");
}
}
// The example displays output like the following:
// Shoes: sells for $19.95

只读和只写属性

省略 set 访问器可使属性为只读,省略 get 访问器可使属性为只写。只读属性对于任何赋值气度都会造成编译错误。例如,下例中是Id为只读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Program {
static void Main () {
Employee employee1 = new Employee ();
employee1.Initialize (42);

// ERROR: Property or indexer 'Employee.Id'
// cannot be assigned to -- it is read-only
//employee1.Id = "490"; //will not compile if you uncomment this line
}
}

class Employee {
public void Initialize (int id) {
// Use field because Id property has no setter,
// it is read-only.
_Id = id.ToString ();
}

// ...
// Id property declaration
public string Id {
get {
return _Id;
}
// No setter provided.
}
private string _Id;
}

上例中采用Employee构造函数(而不是属性)对字段进行赋值(_Id = id)。如果通过属性来赋值,会造成编译错误。

限制访问器可访问性(C# 编程指南)Restricting Accessor Accessibility (C# Programming Guide)

属性或索引器的 getset 部分称为访问器。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program {
static void Main () {
Employee employee1 = new Employee ();
employee1.Initialize (42);
// ERROR: The property or indexer 'Employee.Id'
// cannot be used in this context because the set
// accessor is inaccessible
//employee1.Id = "490"; //will not compile if you uncomment this line
}
}

class Employee {
public void Initialize (int id) {
// Set Id property
Id = id.ToString ();
}

// ...
// Id property declaration
public string Id {
get {
return _Id;
}
// Providing an access modifier is in C# 2.0
// and higher only
private set {
_Id = value;
}
}
private string _Id;
}

为赋值方法指定private修饰符后,属性对于处Employee之外的其他类来说是只读的。在Employee类内部,属性是可读/可写的,所以可在构造器中对属性进行赋值。为取值方法或赋值方法指定访问修饰符时,注意该访问修饰符的“限制性”必须比应用于整个属相的访问修饰符更“严格”。例如,属性声明为较为严格的private,但将它的赋值方法声明为较宽松的public,就会发生编译错误。

属性作为虚字段使用

下例中,Name属性的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using System;
using System.IO;

public class Program {
public static void Main () {
Employee employee1 = new Employee ();

employee1.Name = "Inigo Montoya";
System.Console.WriteLine (employee1.Name);

// ...
}
}

class Employee {
// FirstName property
public string FirstName {
get {
return _FirstName;
}
set {
_FirstName = value;
}
}
private string _FirstName;

// LastName property
public string LastName {
get {
return _LastName;
}
set {
_LastName = value;
}
}
private string _LastName;
// ...

// Name property
public string Name {
get {
return FirstName + " " + LastName;
}
set {
// Split the assigned value into
// first and last names.
string[] names;
names = value.Split (new char[] { ' ' });
if (names.Length == 2) {
FirstName = names[0];
LastName = names[1];
} else {
// Throw an exception if the full
// name was not assigned.
throw new System.ArgumentException (
string.Format (
"Assigned value '{0}' is invalid", value));
}
}
}

// Title property
public string Title { get; set; }

// Manager property
public Employee Manager { get; set; }
}

构造器 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
2
3
4
5
6
7
8
9
10
11
12
13
14
class Employee
{
// Employee constructor
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }
//...

构造函数是一种方法,其名称与其类名``完全相同。 其方法签名仅包含方法名称和其参数列表;它没有返回类型。构造函数是“运行时”用来初始化对象实例的方法。在此例中,构造函数以员工的名字和姓氏作为参数,允许程序员在实例化Employee对象时制定这些参数的值。如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Program
{
public static void Main()
{
Employee employee;
employee = new Employee("Inigo", "Montoya");
employee.Salary = "Too Little";

System.Console.WriteLine(
"{0} {1}: {2}",
employee.FirstName,
employee.LastName,
employee.Salary);
}
}

如果某个构造函数可以作为单个语句实现,则可以使用表达式主体定义。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
2
3
4
5
6
7
8
9
10
11
12
public class Location
{
private string locationName;

public Location(string name) => locationName = name;

public string Name
{
get => locationName;
set => locationName = value;
}
}

默认构造器 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Program {
public static void Main () {

// Declare a StudentName by using the constructor that has two parameters.
StudentName student1 = new StudentName ("Craig", "Playstead");

// Make the same declaration by using an object initializer and sending
// arguments for the first and last names. The default constructor is
// invoked in processing this declaration, not the constructor that has
// two parameters.
StudentName student2 = new StudentName {
FirstName = "Craig",
LastName = "Playstead",
};

// Declare a StudentName by using an object initializer and sending
// an argument for only the ID property. No corresponding constructor is
// necessary. Only the default constructor is used to process object
// initializers.
StudentName student3 = new StudentName {
ID = 183
};

// Declare a StudentName by using an object initializer and sending
// arguments for all three properties. No corresponding constructor is
// defined in the class.
StudentName student4 = new StudentName {
FirstName = "Craig",
LastName = "Playstead",
ID = 116
};

System.Console.WriteLine (student1.ToString ());
System.Console.WriteLine (student2.ToString ());
System.Console.WriteLine (student3.ToString ());
System.Console.WriteLine (student4.ToString ());
}

// Output:
// Craig 0
// Craig 0
// 183
// Craig 116
}

public class StudentName {
// The default constructor has no parameters. The default constructor
// is invoked in the processing of object initializers.
// You can test this by changing the access modifier from public to
// private. The declarations in Main that use object initializers will
// fail.
public StudentName () { }

// The following constructor has parameters for two of the three
// properties.
public StudentName (string first, string last) {
FirstName = first;
LastName = last;
}

// Properties.
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }

public override string ToString () {
return FirstName + " " + ID;
}
}

下面的示例演示如何使用集合初始化器来初始化 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
2
3
4
5
6
7
List<StudentName> students = new List<StudentName>()
{
new StudentName {FirstName="Craig", LastName="Playstead", ID=116},
new StudentName {FirstName="Shu", LastName="Ito", ID=112},
new StudentName {FirstName="Gretchen", LastName="Rivas", ID=113},
new StudentName {FirstName="Rajesh", LastName="Rotti", ID=114}
};

终结器 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
2
3
4
5
6
7
class Car
{
~Car() // destructor
{
// cleanup statements...
}
}

终结器也可以作为表达式主体定义实现,如下面的示例所示。A finalizer can also be implemented as an expression body definition, as the following example shows.

1
2
3
4
5
6
7
8
using System;

public class Destroyer
{
public override string ToString() => GetType().Name;

~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");
}

程序员无法控制何时调用终结器,因为这由垃圾回收器决定。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Employee {
public Employee (string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
}

public Employee (
int id, string firstName, string lastName) {
Id = id;
FirstName = firstName;
LastName = lastName;
}

public Employee (int id) {
Id = id;

// Look up employee name...
// ...
}

public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }

// ...
}

应优先使用可选参数而不是重载,以便在API中清楚地看出“默认”属性的默认值。例如,Person的一个构造器签名Person(string firstName, string lastName, int? age = null)就清楚地指明如果Person的Age未指定,就将它默认为null。

构造器链:使用this调用另一个构造器

上例中,对Employee对象进行初始化的代码多处重复,可以从一个构造器中调用另一个构造器,避免重复输入代码。这称为构造器链,它是用构造器初始化器来实现的。C#采用的语法格式是在一个冒号后面添加this关键字,再添加被调用构造器的参数列表。构造器初始化器在自行当前的构造器实现之前,判断要调用另外哪一个构造器,实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Employee {
public Employee (string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
}

public Employee (
int id, string firstName, string lastName) : this (firstName, lastName) {
Id = id;
}

public Employee (int id) {
Id = id;

// Look up employee name...
// ...

// NOTE: Member constructors cannot be
// called explicitly inline
// this(id, firstName, lastName);
}

public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }

// ...
}

上例中,3个参数的构造器调用2个参数的构造器。通常情况下,用参数最少的构造器调用参数最多的构造器,为未知的参数传递默认值。如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Employee(int id):this(id,"","")
{
string firstName;
string lastName;
Id = id;

// 检索、组合Employee数据
firstName = string.Empty;
lastName = string.Empty;
// ...

Initialize(id, firstName, lastName);
}

构造器链完整例子参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;

public class Person {
public string personName;
//定义年龄为可空类型,这样就可以赋予其null值
public int? personAge;
//下面前三个构造函数都是去调用参数最多的第四个构造函数,只取它们所需要的部分参数即可
//这样的做法就是this串联构造函数
public Person () : this ("", 0) {

}
public Person (string name) : this (name, null) {

}
public Person (int age) : this ("", age) {

}
public Person (string name, int? age) {
this.personName = name;
//通过 ?? 判断传入的age是否null值
//如果属于null值,则赋值100
this.personAge = age ?? 100;
}
public void Display () {
Console.WriteLine ("Name:{0},Age:{1}", personName, personAge);
}
}

class Hello {
static void Main (string[] args) {
Person per1 = new Person ();
per1.Display ();
Person per2 = new Person (20);
per2.Display ();
Person per3 = new Person ("evan");
per3.Display ();
Person per4 = new Person ("evan", 20);
per4.Display ();
Console.ReadKey ();
}
}

初学者主题:集中初始化

创建单独的方法,将所有初始化代码集中在一起,如下例中,创建名为Initialize()方法,它同时获取员工的名字、姓氏和ID。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Employee {
public Employee (string firstName, string lastName) {
int id;
// Generate an employee ID...
id = 0; // id needs to be initialized for this example
// ...
Initialize (id, firstName, lastName);
}

public Employee (int id, string firstName, string lastName) {
Initialize (id, firstName, lastName);
}

public Employee (int id) {
string firstName;
string lastName;
Id = id;

// Look up employee data
firstName = string.Empty;
lastName = string.Empty;
// ...

Initialize (id, firstName, lastName);
}

private void Initialize (
int id, string firstName, string lastName) {
Id = id;
FirstName = firstName;
LastName = lastName;
}
// ...

private int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

匿名类型

C# 3.0引入了对匿名类型的支持,匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;

public class Program {
public static void Main () {
var patent1 =
new {
Title = "Bifocals",
YearOfPublication = "1784"
};
var patent2 =
new {
Title = "Phonograph",
YearOfPublication = "1877"
};
var patent3 =
new {
patent1.Title,
Year = patent1.YearOfPublication
};

System.Console.WriteLine ("{0} ({1})",
patent1.Title, patent1.YearOfPublication);
System.Console.WriteLine ("{0} ({1})",
patent2.Title, patent1.YearOfPublication);

Console.WriteLine ();
Console.WriteLine (patent1);
Console.WriteLine (patent2);

Console.WriteLine ();
Console.WriteLine (patent3);
}
}
/* 输出
ifocals (1784)
Phonograph (1784)

{ Title = Bifocals, YearOfPublication = 1784 }
{ Title = Phonograph, YearOfPublication = 1877 }

{ Title = Bifocals, Year = 1784 }
*/

匿名类型通常用在查询表达式的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Automobile
{
public static int NumberOfWheels = 4;
public static int SizeOfGasTank
{
get
{
return 15;
}
}
public static void Drive() { }
public static event EventType RunOutOfGas;

// Other non-static fields and properties...
}

在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。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
2
Automobile.Drive();
int i = Automobile.NumberOfWheels;

如果类包含静态字段,则提供在类加载时初始化它们的静态构造函数。

常量或类型声明是隐式的静态成员。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
2
3
4
5
6
7
public class MyBaseC
{
public struct MyStruct
{
public static int x = 100;
}
}

若要引用静态成员 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;

class Test {
static int x = y;
static int y = 5;

static void Main () {
Console.WriteLine (x);
Console.WriteLine (y);

x = 99;
Console.WriteLine (x);
}
}
/*
Output:
0
5
99
*/

静态字段

使用static关键字声明一个静态字段,然后访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;

public class Program {
public static void Main () {
Employee.NextId = 1000000;

Employee employee1 = new Employee (
"Inigo", "Montoya");
Employee employee2 = new Employee (
"Princess", "Buttercup");

Console.WriteLine (
"{0} {1} ({2})",
employee1.FirstName,
employee1.LastName,
employee1.Id);
Console.WriteLine (
"{0} {1} ({2})",
employee2.FirstName,
employee2.LastName,
employee2.Id);

Console.WriteLine ("NextId = {0}", Employee.NextId);
}
}

class Employee {
public Employee (string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
Id = NextId;
NextId++;
}

public static int NextId = 42;
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }
}
/* 输出
Inigo Montoya (1000000)
Princess Buttercup (1000001)
NextId = 1000002
*/

NextId字段声明包含static修饰符,成为静态字段静态字段实例字段一样可以在声明时进行初始化。静态字段只属于类本身,从类外部访问静态字段时要使用类名,而不是类实例(变量)名。只有在类(或者派生类)内部代码中,才可以省略类名。

静态方法

Console.WriteLine()这些,就是静态方法。无需实例话,直接引用。

静态构造器

C#支持静态构造器,不允许有任何参数。使用静态构造器将类中的静态数据初始化成特定的值,尤其是无法通过声明时的一次简单赋值来获得初始值的时候。

声明静态构造器

1
2
3
4
5
6
7
8
9
10
11
12
using System;

class Employee {
static Employee () {
Random randomGenerator = new Random ();
NextId = randomGenerator.Next (101, 999);
}

// ...
public static int NextId = 42;
// ...
}

在静态构造器中进行的赋值,优先于声明时的赋值,这和实例字段情况一样。不要在静态构造器中抛出异常,这会造成类型在用用程序的剩余生存期内无法使用。

静态属性

可以将属性声明为static,使用静态属性几乎肯定比使用公共静态字段好,因为公共静态字段在任何地方都能调用,而静态属性至少提供了一定程度的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Employee {
// ...
public static int NextId {
get {
return _NextId;
}
private set {
_NextId = value;
}
}
public static int _NextId = 42;
// ...
}

静态类

静态类无法实例化。 换句话说,无法使用 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;

public class Program {
public static void Main () {
int[] arrayNum = { 11, 12, 13, 14, 15, 16 };
int[] arrayNumber = new int[] { 1, 2, 3, 4, 5, 6 };

System.Console.WriteLine (SimpleMath.Max (arrayNumber));
System.Console.WriteLine (SimpleMath.Min (arrayNum));
}
}

public static class SimpleMath {
// params allows the number of parameters to vary.
public static int Max (params int[] numbers) {
// Check that there is a least one item in numbers.
if (numbers.Length == 0) {
throw new ArgumentException (
"numbers cannot be empty");
}

int result;
result = numbers[0];
foreach (int number in numbers) {
if (number > result) {
result = number;
}
}
return result;
}

// params allows the number of parameters to vary.
public static int Min (params int[] numbers) {
// Check that there is a least one item in numbers.
if (numbers.Length == 0) {
throw new ArgumentException (
"numbers cannot be empty");
}

int result;
result = numbers[0];
foreach (int number in numbers) {
if (number < result) {
result = number;
}
}
return result;
}
}

扩展方法

转自 田小计划

当我们想为一个现有的类型添加一个方法的时候,有两种方式:一是直接在现有类型中添加方法;但是很多情况下现有类型都是不允许修改的,那么可以使用第二种方式,基于现有类型创建一个子类,然后在子类中添加想要的方法。

当C# 2.0中出现了静态类之后,对于上面的问题,我们也可以创建静态工具类来实现想要添加的方法。这样做可以避免创建子类,但是在使用时代码就没有那么直观了。

其实,上面的方法都不是很好的解决办法。在C# 3.0中出现了扩展方法,通过扩展方法我们可以直接在一个现有的类型上”添加”方法。当使用扩展方法的时候,可以像调用实例方法一样的方式来调用扩展方法。

扩展方法的声明和调用

相比普通方法,扩展方法有它自己的特征,下面就来看看怎么声明一个扩展方法:

  • 它必须在一个非嵌套、非泛型的静态类中(所以扩展方法一定是静态方法)
  • 它至少要有一个参数
  • 第一个参数必须加上this关键字作为前缀
    • 第一个参数类型也称为扩展类型(extended type),表示该方法对这个类型进行扩展
  • 第一个参数不能用其他任何修饰符(比如out或ref)
  • 第一个参数的类型不能是指针类型

根据上面的要求,我们给int类型添加了一个扩展方法,用来判断一个int值是不是偶数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

namespace ExtentionMethodTest {
public static class ExtentionMethods {
public static bool IsEven (this int num) {
return num % 2 == 0;
}
}

class Program {
static void Main (string[] args) {
int num = 10;
//直接调用扩展方法
Console.WriteLine ("Is {0} a even number? {1}", num, num.IsEven ());
num = 11;
//直接调用扩展方法
Console.WriteLine ("Is {0} a even number? {1}", num, num.IsEven ());
//通过静态类调用静态方法
Console.WriteLine ("Is {0} a even number? {1}", num, ExtentionMethods.IsEven (num));

Console.Read ();
}
}
}
/* 输出
Is 10 a even number? True
Is 11 a even number? False
Is 11 a even number? False
*/

通过上面的例子可以看到,当调用扩展方法的时候,可以像调用实例方法一样。这就是我们使用扩展方法的原因之一,我们可以给一个已有类型”添加”一个方法。

既然扩展方法是一个静态类的方法,我们当然也可以通过静态类来调用这个方法。

通过IL可以看到,其实扩展方法也是编译器为我们做了一些转换,将扩展方法转化成静态类的静态方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
.method private hidebysig static
void Main (
string[] args
) cil managed {
// Method begins at RVA 0x2068
// Code size 98 (0x62)
.maxstack 3
.entrypoint
.locals init (
[0] int32
)

IL_0000 : nop
IL_0001 : ldc.i4.s 10
IL_0003 : stloc .0
IL_0004 : ldstr "Is {0} a even number? {1}"
IL_0009 : ldloc .0
IL_000a : box[System.Runtime] System.Int32
IL_000f : ldloc .0
//直接调用扩展方法
IL_0010 : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32)
IL_0015 : box[System.Runtime] System.Boolean
IL_001a : call void[System.Console] System.Console::WriteLine (string, object, object)
IL_001f : nop
IL_0020 : ldc.i4.s 11
IL_0022 : stloc .0
IL_0023 : ldstr "Is {0} a even number? {1}"
IL_0028 : ldloc .0
IL_0029 : box[System.Runtime] System.Int32
IL_002e : ldloc .0
//直接调用扩展方法
IL_002f : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32)
IL_0034 : box[System.Runtime] System.Boolean
IL_0039 : call void[System.Console] System.Console::WriteLine (string, object, object)
IL_003e : nop
IL_003f : ldstr "Is {0} a even number? {1}"
IL_0044 : ldloc .0
IL_0045 : box[System.Runtime] System.Int32
IL_004a : ldloc .0
//通过静态类调用静态方法
IL_004b : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32)
IL_0050 : box[System.Runtime] System.Boolean
IL_0055 : call void[System.Console] System.Console::WriteLine (string, object, object)
IL_005a : nop
IL_005b : call int32[System.Console] System.Console::Read ()
IL_0060 : pop
IL_0061 : ret
} // end of method Program::Main

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.

枚举类型使你能够为整数内置类型定义命名常量(例如 intuintlong 等)。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
2
3
4
class Calendar1
{
public const int months = 12;
}

可以同时声明多个同一类型的常量,例如:Multiple constants of the same type can be declared at the same time, for example:

1
2
3
4
class Calendar2
{
const int months = 12, weeks = 52, days = 365;
}

常量字段自动成为静态字段,将常量字段显式声明为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
2
3
4
5
6
7
8
9
10
11
12
class Age
{
readonly int _year;
Age(int year)
{
_year = year;
}
void ChangeYear()
{
//_year = 1967; // Compile error if uncommented.
}
}

readonly 关键字不同于 const 关键字。const 字段只能在该字段的声明中初始化。

readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,虽然 const 字段是编译时常量,但 readonly 字段可用于运行时常量,如下面的示例所示:

1
public static readonly uint timeStamp = (uint)DateTime.Now.Ticks;

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;

public class ReadOnlyTest {
class SampleClass {
public int x;
// Initialize a readonly field
public readonly int y = 25;
public readonly int z;

public SampleClass () {
// Initialize a readonly instance field
z = 24;
}

public SampleClass (int p1, int p2, int p3) {
x = p1;
y = p2;
z = p3;
}
}

static void Main () {
SampleClass p1 = new SampleClass (11, 21, 32); // OK
Console.WriteLine ("p1: x={0}, y={1}, z={2}", p1.x, p1.y, p1.z);
SampleClass p2 = new SampleClass ();
p2.x = 55; // OK
Console.WriteLine ("p2: x={0}, y={1}, z={2}", p2.x, p2.y, p2.z);
}
}
/*
Output:
p1: x=11, y=21, z=32
p2: x=55, y=25, z=24
*/

在前面的示例中,如果使用如下的语句:

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
2
3
4
5
6
7
8
9
10
11
12
13
using System;

class CommonGuid {
public static readonly Guid ComIUnknownGuid =
new Guid ("00000000-0000-0000-C000-000000000046");
public static readonly Guid ComIClassFactoryGuid =
new Guid ("00000001-0000-0000-C000-000000000046");
public static readonly Guid ComIDispatchGuid =
new Guid ("00020400-0000-0000-C000-000000000046");
public static readonly Guid ComITypeInfoGuid =
new Guid ("00020401-0000-0000-C000-000000000046");
// ...
}

嵌套类

构造中定义的类型称为嵌套类型。A type defined within a class or struct is called a nested type. 例如:

1
2
3
4
5
6
7
class Container
{
class Nested
{
Nested() { }
}
}

不论外部类型是类还是构造,嵌套类型均默认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 类为 public:The following example makes the Nested class public:

1
2
3
4
5
6
7
class Container
{
public class Nested
{
Nested() { }
}
}

分部类和方法 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
2
3
4
5
6
7
// File:Program1.cs
public partial class Employee
{
public void DoWork()
{
}
}
1
2
3
4
5
6
7
// File:Program2.cs
public partial class Employee
{
public void GoToLunch()
{
}
}

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. 各个部分必须具有相同的可访问性,如 publicprivate 等。

嵌套类型是可以分部的:

1
2
3
4
5
6
7
8
9
10
11
12
13
// File: Program.cs
partial class Program
{
static void Main(string[] args)
{
CommandLine commandLine = new CommandLine(args);

switch(commandLine.Action)
{
// ...
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// File: Program+CommandLine.cs
partial class Program
{
// Define a nested class for processing the command line.
private class CommandLine
{
public CommandLine(string[] args)
{
//not implemented
}

// ...
public int Action
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
}
}

分部方法 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
2
3
4
5
6
7
// Definition in file1.cs
public partial class Person
{
partial void onNameChanged();

// ...
}
1
2
3
4
5
6
7
8
// Implementation in file2.cs
public partial class Person
{
partial void onNameChanged()
{
// method body
}
}
  • 分部方法声明必须以上下文关键字 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.

  • 分部方法可以有 staticunsafe 修饰符。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.

  • 你可以为已定义并实现的分部方法生成委托,但不能为已经定义但未实现的分部方法生成委托。

结尾