完成了 C# 入门之后,就可以进入 C# 基础阶段。

一、命令行参数

命令行会给 C# 程序的 Main 方法以字符串数组的形式传递参数(可选),正如 Main 的方法签名通常为 static void Main(string[] args)

默认情况下,命令行使用空格分隔相邻的参数。如果一个参数里面包含空格,则需要用双引号包裹这个参数,如:

命令行上的输入 传递给 Main 的字符串数组
executable.exe a b c "a"
"b"
"c"
executable.exe one two "one"
"two"
executable.exe "one two" three "one two"
"three"

这个例子调用了命令行参数:

1
2
3
4
5
6
Console.WriteLine($"parameter count = {args.Length}");

for (int i = 0; i < args.Length; i++)
{
Console.WriteLine($"Arg[{i}] = [{args[i]}]");
}

二、C# 的面向对象编程(一):抽象和封装

2.1 定义类

从这个例子入手,在工程中新建一个 .cs 文件,里面写上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace CustomizedClasses;

public class BankAccount
{
public string Number { get; } // 属性
public string Owner { get; set; }
public decimal Balance { get; }

public void MakeDeposit(decimal amount, DateTime date, string note) // 方法
{
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
}
}

namespace 声明了一个命名空间,组织其后面跟随的类。

public class BankAccount 定义创建的类,大括号包裹的类体中定义类的成员。publlic 允许被外部命名空间或程序集访问。

此时 BankAccount 有 5 个成员。前三个是属性,后面两个是方法。属性就是带有访问器 { get; set; } 的数据成员,方法就是类体中可复用的代码块。

因此,类定义就是在命名空间中定义集成各个类成员的类型系统。

2.2 构造函数

在前面的例子中,给 MakeDeposit 方法声明前面添加:

1
2
3
4
5
public BankAccount(string name, decimal initialBalance)  // 构造函数
{
this.Owner = name;
this.Balance = initialBalance;
}

这就是一个构造函数,使用类名做标识符。它在实例化时被调用,this 表示类的实例,如果没有局部变量冲突可以省略。此时可以在顶级语句或 Main 方法里面写:

1
2
3
4
5
6
using CustomizedClasses;

var account1 = new BankAccount("name1", 1000);
Console.WriteLine($"Account {account1.Number} was created for {account1.Owner} with {account1.Balance} initial balance.");
var account2 = new BankAccount("name2", 2000);
Console.WriteLine($"Account {account2.Number} was created for {account2.Owner} with {account2.Balance} initial balance.");

此时已经把刚才创建的类进行了实例化,也就是从账户类这个模板创建了一个账户,并初始化了名字和余额。但是此时账号为空。

2.3 字段

给前面的 BankAccount 类体添加:

1
private static int s_accountNumberSeed = 1234567890;  // 字段

此时定义了字段 accountNumberSeed

该字段使用 private static 修饰,private 意味着它只能在类体被访问,static 意味着它由所有实例共享。

s_ 前缀是根据 C# 命名约定写上的,s 表示 static,而 _ 表示 private

大多数情况下,我们使用 public 修饰属性,使用 private 修饰字段。

添加以下代码至构造函数,就可以实现账号分配:

1
2
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

调试程序,可见输出:

1
2
Account 1234567890 was created for name1 with 1000 initial balance.
Account 1234567891 was created for name2 with 2000 initial balance.

2.4 定义多个类并交互

定义多一个类,表示交易:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Transaction
{
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }

public Transaction(decimal amount, DateTime date, string note)
{
Amount = amount;
Date = date;
Notes = note;
}
}

BankAccount 类添加一个交易记录列表:

1
private List<Transaction> _allTransactions = new();

Balance 属性替换为

1
2
3
4
5
6
7
8
9
10
11
12
13
public decimal Balance
{
get
{
decimal balance = 0;
foreach (var item in _allTransactions)
{
balance += item.Amount;
}

return balance;
}
}

此时由于定义了 get 访问器,所以 Balance 属性不再是自动属性,而是只读属性,它是一个被计算而得的属性。

写入 BankAccount 存款和取款的方法(前面声明过,此时填入即可):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void MakeDeposit(decimal amount, DateTime date, string note)  // 存款
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
} // 防止金额小于零
var deposit = new Transaction(amount, date, note);
_allTransactions.Add(deposit); // 记录一个交易记录
}
public void MakeWithdrawal(decimal amount, DateTime date, string note) // 取款
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
} // 防止金额小于零
if (Balance - amount < 0)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
} // 防止刻意透支
var withdrawal = new Transaction(-amount, date, note);
_allTransactions.Add(withdrawal); // 记录一个交易记录
}

因为 Balance 属性变为了只读,所以无法被赋予值,BankAccount 的构造函数此时也应修改为:

1
2
3
4
5
6
7
8
public BankAccount(string name, decimal initialBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

回到顶级语句或 Main 方法,添加调试代码:

1
2
3
4
account1.MakeWithdrawal(300, DateTime.Now, "Rent payment");
Console.WriteLine(account1.Balance);
account1.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account1.Balance);

输出内容如下:

1
2
3
4
Account 1234567890 was created for name1 with 1000 initial balance.
Account 1234567891 was created for name2 with 2000 initial balance.
700
800

回顾刚才的代码,进行一点修饰并添加查看交易记录的功能,可得两个 .cs 文件:

BankAccount.cs

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
namespace CustomizedClasses;

public class BankAccount // 账户类
{
private static int s_accountNumberSeed = 1234567890; // 账号种子字段
public string Number { get; } // 账号属性
public string Owner { get; set; } // 用户名属性
public decimal Balance // 余额属性
{
get // 根据交易记录计算
{
decimal balance = 0;
foreach (var item in _allTransactions)
{
balance += item.Amount;
}

return balance;
}
}
private List<Transaction> _allTransactions = new(); // 交易记录属性
public BankAccount(string name, decimal initialBalance) // 账户构造函数
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "初始余额");
}
public void MakeDeposit(decimal amount, DateTime date, string note) // 存款方法
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
} // 防止金额小于零
var deposit = new Transaction(amount, date, note);
_allTransactions.Add(deposit); // 记录一个交易记录
}
public void MakeWithdrawal(decimal amount, DateTime date, string note) // 取款方法
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
} // 防止金额小于零
if (Balance - amount < 0)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
} // 防止刻意透支
var withdrawal = new Transaction(-amount, date, note);
_allTransactions.Add(withdrawal); // 记录一个交易记录
}
public string GetAccountHistory() // 获取交易记录方法
{
var report = new System.Text.StringBuilder();

decimal balance = 0;
report.AppendLine("日期\t\t交易\t余额\t备注");
foreach (var item in _allTransactions)
{
balance += item.Amount;
report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
}

return report.ToString();
}
}

public class Transaction // 交易类
{
public decimal Amount { get; } // 金额属性
public DateTime Date { get; } // 日期属性
public string Notes { get; } // 备注属性

public Transaction(decimal amount, DateTime date, string note)
{
Amount = amount;
Date = date;
Notes = note;
}
}

Program.cs

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

var account1 = new BankAccount("Tim", 1000); // 创建一个账户
Console.WriteLine($"用户 {account1.Number}{account1.Owner} 初始化了 {account1.Balance} 的金额.");
var account2 = new BankAccount("Jane", 2000); // 创建另一个账户
Console.WriteLine($"用户 {account2.Number}{account2.Owner} 初始化了 {account2.Balance} 的金额.");

account1.MakeWithdrawal(300, DateTime.Now, "买车票");
Console.WriteLine($"{account1.Owner} 支出了" + account1.Balance);
account1.MakeDeposit(100, DateTime.Now, "补贴");
Console.WriteLine($"{account1.Owner} 存入了" + account1.Balance);

Console.WriteLine(account1.GetAccountHistory());

三、C# 的面向对象编程(二):继承和多态

3.1 创建类的继承

继续完善刚才的银行系统。根据银行账户存在不同需求,于是请求以下账户类型:

  • 在每个月月末获得利息的红利账户

  • 余额可以为负,但存在余额时会产生每月利息的信用账户

  • 以单笔存款开户且只能用用于支付预付礼品的礼品卡账户,月初可充值一次

通过这样的语法,我们可以创建对 BankAccount 类的三种继承:

1
2
3
4
5
6
7
8
9
10
11
public class InterestEarningAccount : BankAccount  // 红利账户
{
}

public class LineOfCreditAccount : BankAccount // 信用账户
{
}

public class GiftCardAccount : BankAccount // 礼品卡账户
{
}

最好从不同的源文件中创建每个类,因此我们可以新建三个和这三种账户类名同名的源文件,然后把类声明放进去。

3.2 构造函数的继承

编译器不会生成被继承的构造函数,因此需要派生类显式调用此构造函数,以下代码显示了 InterestEarningAccount 类的构造函数:

1
2
3
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}

3.3 方法多态

BankAccount 类添加 PerformMonthEndTransactions 方法:

1
public virtual void PerformMonthEndTransactions() { }  // 进行月末交易

基类中的方法使用 virtual 修饰符允许派生类覆盖重写该方法,以实现多态。

从月末交易方法的角度,再描述前面提出的三种账户派生类的要求:

  • 红利账户:月末获得 2% 的利息

  • 信用账户:余额可为负但不得大于信用限额的绝对值,若月末不为 0 都可以产生利息

  • 礼品卡账户:每月最后一天可以充值一次指定金额

随后,依次给三个账户派生类也添加 PerformMonthEndTransactions 方法。

InterestEarningAccount 类开始:

1
2
3
4
5
6
7
8
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "按月放息");
}
}

然后是 LineOfCreditAccount 类:

1
2
3
4
5
6
7
8
public override void PerformMonthEndTransactions()
{
if (Balance < 0)
{
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "按月收息");
}
}

而在 GiftCardAccount 类需要先修改构造函数,并加一个只读字段 _monthlyDeposit

1
2
3
4
private readonly decimal _monthlyDeposit = 0m;

public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;

只读字段用 readonly 修饰,只能在声明时初始化或在构造函数中赋值。

然后添加 PerformMonthEndTransactions 方法:

1
2
3
4
5
6
7
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "余额充值");
}
}

给顶级语句添加代码进行调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Console.WriteLine("----------");

var giftCard = new GiftCardAccount("礼品卡1", 100, 50);
giftCard.MakeWithdrawal(20, DateTime.Now, "买咖啡");
giftCard.MakeWithdrawal(50, DateTime.Now, "买杂货");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "追加消费");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("储蓄卡1", 10000);
savings.MakeDeposit(750, DateTime.Now, "存些钱");
savings.MakeDeposit(1250, DateTime.Now, "再存些钱");
savings.MakeWithdrawal(250, DateTime.Now, "支付每月账单");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

由于信用卡允许余额为负,而原来的构造函数不允许初始余额小于零,我们可以在 BankAccount 类中定义一个只读字段 _minimunBalance 然后重载构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private readonly decimal _minimumBalance;

public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }

public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "初始余额");
}

MakeWithdrawl 方法的 if 及条件也要改为

1
if (Balance - amount < _minimumBalance)

相应地,LineCreditAccount 类的构造函数也要改为

1
2
3
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}

添加透支规则,让信用卡有偿透支,把原来的

1
2
3
4
5
6
7
8
9
10
11
12
13
public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
if (Balance - amount < _minimumBalance)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
_allTransactions.Add(withdrawal);
}

替换为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
}

protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
else
{
return default;
}
}

protect 修饰符表示它只能在派生类中被调用。? 表示它允许为 null

并在 LineOfCreditAccount 类中添加

1
2
3
4
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "申请透支费用")
: default;

随后在顶级语句测试

1
2
3
4
5
6
7
8
var lineOfCredit = new LineOfCreditAccount("信用卡1", 0, 2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "每月预支");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "还点小钱");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "紧急维修资金");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "部分修复");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

此时各源文件的内容如下。

Program.cs

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

var account1 = new BankAccount("Tim", 1000); // 创建一个账户
Console.WriteLine($"用户 {account1.Number}{account1.Owner} 初始化了 {account1.Balance} 的金额.");
var account2 = new BankAccount("Jane", 2000); // 创建另一个账户
Console.WriteLine($"用户 {account2.Number}{account2.Owner} 初始化了 {account2.Balance} 的金额.");

account1.MakeWithdrawal(300, DateTime.Now, "买车票");
Console.WriteLine($"{account1.Owner} 支出了" + account1.Balance);
account1.MakeDeposit(100, DateTime.Now, "补贴");
Console.WriteLine($"{account1.Owner} 存入了" + account1.Balance);

Console.WriteLine(account1.GetAccountHistory());

Console.WriteLine("----------");

var giftCard = new GiftCardAccount("礼品卡1", 100, 50);
giftCard.MakeWithdrawal(20, DateTime.Now, "买咖啡");
giftCard.MakeWithdrawal(50, DateTime.Now, "买杂货");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "追加消费");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("储蓄卡1", 10000);
savings.MakeDeposit(750, DateTime.Now, "存些钱");
savings.MakeDeposit(1250, DateTime.Now, "再存些钱");
savings.MakeWithdrawal(250, DateTime.Now, "支付每月账单");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

Console.WriteLine("----------");

var lineOfCredit = new LineOfCreditAccount("信用卡1", 0, 2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "每月预支");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "还点小钱");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "紧急维修资金");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "部分修复");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

BankAccount.cs

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
namespace CustomizedClasses;

public class BankAccount // 账户类
{
private readonly decimal _minimumBalance;
private static int s_accountNumberSeed = 1234567890; // 账号种子字段
public string Number { get; } // 账号属性
public string Owner { get; set; } // 用户名属性

public virtual void PerformMonthEndTransactions() { } // 进行月末交易
public decimal Balance // 余额属性
{
get // 根据交易记录计算
{
decimal balance = 0;
foreach (var item in _allTransactions)
{
balance += item.Amount;
}

return balance;
}
}
private List<Transaction> _allTransactions = new(); // 交易记录属性
public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }
public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "初始余额");
}
public void MakeDeposit(decimal amount, DateTime date, string note) // 存款方法
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
} // 防止金额小于零
var deposit = new Transaction(amount, date, note);
_allTransactions.Add(deposit); // 记录一个交易记录
}
public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
}
protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
else
{
return default;
}
}
public string GetAccountHistory() // 获取交易记录方法
{
var report = new System.Text.StringBuilder();

decimal balance = 0;
report.AppendLine("日期\t\t交易\t余额\t备注");
foreach (var item in _allTransactions)
{
balance += item.Amount;
report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
}

return report.ToString();
}
}

public class Transaction // 交易类
{
public decimal Amount { get; } // 金额属性
public DateTime Date { get; } // 日期属性
public string Notes { get; } // 备注属性

public Transaction(decimal amount, DateTime date, string note)
{
Amount = amount;
Date = date;
Notes = note;
}
}

InterestEarningAccount.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace CustomizedClasses;

public class InterestEarningAccount : BankAccount
{
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "按月放息");
}
}
}

LineOfCreditAccount.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace CustomizedClasses;

public class LineOfCreditAccount : BankAccount
{
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}
public override void PerformMonthEndTransactions()
{
if (Balance < 0)
{
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "按月收息");
}
}
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "申请透支费用")
: default;
}

GiftCardAccount.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace CustomizedClasses;

public class GiftCardAccount : BankAccount
{
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "余额充值");
}
}
}

四、深入理解继承

4.1 继承的概念

继承,就是派生类对基类的延续。C# 只支持单一继承。

继承就是继承成员,除了静态构造函数、实例构造函数、终结器。

继承的成员是否可见取决于它们的可访问性,如嵌套派生类才可以访问基类的私有成员,就像:

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
public class A
{
private int _value = 10;

public class B : A
{
public int GetValue()
{
return _value;
}
}
}

public class C : A
{
// public int GetValue()
// {
// return _value;
// }
}

public class AccessExample
{
public static void Main(string[] args)
{
var b = new A.B();
Console.WriteLine(b.GetValue());
}
}
// The example displays the following output:
// 10

继承通常需要方法重写,方法重写就是在派生类用 override 修饰的方法重写基类中的虚方法,虚方法可以被 virtualoverrideabstract 修饰。virtual 允许方法被重写,而 abstract 要求派生类必须重写该方法。

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class A
{
public abstract void Method1();
}

public class B : A // Generates CS0534. 如果 override Method1() 可以解决
{
public void Method3()
{
// Do something.
}
}

4.2 隐式继承

定义一个空类:

1
2
public class SimpleClass
{ }

其实这个空类已经有九个成员,是因为它已经隐式继承了 Object 类。

4.3 继承表示 “is a”

继承表示 “is a”,也就是派生类是基类的一个特定版本。就像香蕉 “is a” 水果。

4.4 设计抽象基类

许多情况下不希望基类提供实现代码,而是用于声明抽象方法,这时它就是个抽象基类。但由于实例化抽象基类无意义,此时用 abstract 修饰该类。

就像下面的 Shape 抽象基类,提供面积和周长两个属性:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Shape
{
public abstract double Area { get; }

public abstract double Perimeter { get; }

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

public static double GetArea(Shape shape) => shape.Area;

public static double GetPerimeter(Shape shape) => shape.Perimeter;
}

给它派生为三种图形:

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
using System;

public class Square : Shape
{
public Square(double length)
{
Side = length;
}

public double Side { get; }

public override double Area => Math.Pow(Side, 2);

public override double Perimeter => Side * 4;

public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
}

public class Rectangle : Shape
{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}

public double Length { get; }

public double Width { get; }

public override double Area => Length * Width;

public override double Perimeter => 2 * Length + 2 * Width;

public bool IsSquare() => Length == Width;

public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
}

public class Circle : Shape
{
public Circle(double radius)
{
Radius = radius;
}

public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);

public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);

// Define a circumference, since it's the more familiar term.
public double Circumference => Perimeter;

public double Radius { get; }

public double Diameter => Radius * 2;
}

执行它。

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
using System;

public class Example
{
public static void Main()
{
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (Shape shape in shapes)
{
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
if (shape is Rectangle rect)
{
Console.WriteLine($" Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
continue;
}
if (shape is Square sq)
{
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
}
}
}
}
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85