面向对象编程之C#
学些面向对象编程语言已经有一段时间了。现在借学习C#的机会把这方面的知识汇总归纳一下。
首先在这种思想指导下,把任何事物都高度抽象成为一个一个的类,每个类都具有它自己的字段和属性以
及方法。每个类在被实例化之后就变成一个具体的对象,每个同类的对象在结构上是一致的,不同的是他
们各自的字段和属性的值是不同的。如果一个类有公共的静态变量和静态方法。那么就算这个类没有被实
例化也可以使用这些公共的静态方法和变量。
类是可以进行继承的,通过继承可以在一组相似类之间,建立一种父子关系。父类于子类之间的关系既有
相同又有不同。子类继承自父类,所以拥有父类一切特征,但是子类也有自己的个性,可以在需要时重载
父类的特性例如:方法。这样相同的方法名,但父类和子类可以有不同的处理方式得到不同的结果。
C#支持实现继承,但不支持多重实现继承。也就是说一个类之能继承自另一个类,不能继承自多个类。C#
还支持接口继承,并且允许多重接口继承。所以虽然一个类不能继承自多个类,但可以继承自多个接口,
并且实现这些接口。接口继承通常被看作一种契约:通过类型派生于接口,从而确保为客户提供某个功能
。
要声明一个类继承自另一个类,语法如下:
class MyClass : BaseClass
{
......
}
要声明一个类继承自另一个类和其他多个接口,语法如下:
public class MyClass : BaseClass, IInterface1,IInterface2
{
......
}
虚方法
可以将基类中的方法或者属性声明为 virtual。这样就可以在继承自他的子类中重载这些方法或属性。与
C++不同的是,在C#中重载必须使用 override 关键字显示声明。这样可以避免C++中易犯的一个错误,就
是重载声明的方法或属性在基类中没有对应的声明。在C#中这会产生一个编译错误。
如果不将基类的方法或属性声明为 virtual,而在子类中使用了相同声明的方法或属性,那么编译时就会
产生一个警告。告诉你基类中的有关方法被子类隐藏了。这不是错误程序依然可以执行,但容易引起误会
。因为此时相同的方法名返回何种结果就要看对象的类型了。如果是基类对象就按照基类的方法返回,如
果是子类对象就按照子类方法返回。要去掉这样的警告可以在子类的同名方法或属性申明时加上 new 关
键字,来显示的隐藏基类的同名方法。
另外可以使用 base.<方法/属性> 在子类中调用基类的方法或属性。
例如:Program.c
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Lesson1
6 {
7 class BaseClass
8 {
9 private string clsName;
10 public virtual string Name
11 {
12 get { return clsName; }
13 set { clsName = value; }
14 }
15
16 public virtual string VirtualMethod1()
17 { return "This is BaseClass VirtualMethod1."; }
18
19 public virtual string VirtualMethod2()
20 { return "This is BaseClass VirtualMethod2."; }
21
22 public string Method()
23 { return "This is BaseClass Method."; }
24 }
25
26 class MyClass : BaseClass
27 {
28 public override string VirtualMethod1()
29 {
30 return "This is MyClass OverrideMethod from BaseClass VirtualMethod1.";
31 }
32
33 public override string Name
34 {
35 get { return base.Name; }
36 set { base.Name = value; }
37 }
38
39 // 警告 1 'Lesson1.MyClass.Method()' hides inherited member
40
41 'Lesson1.BaseClass.Method()'.
42 // Use the new keyword if hiding was intended.
43 // D:\Docoment\Visual Studio 2005\Projects\Lesson1\Lesson1\Program.cs 37
44
45 23 Lesson1
46 public new string Method()
47 {
48 string rs = base.Method();
49 return "This is MyClass Method. " + rs;
50 }
51 }
52
53 class Program
54 {
55 static void Main(string[] args)
56 {
57 BaseClass b = new BaseClass();
58 MyClass a = new MyClass();
59
60 b.Name = "BaseClass";
61 a.Name = "MyClass";
62
63 Console.WriteLine(b.Name + " " + b.Method());
64 Console.WriteLine(a.Name + " " + a.VirtualMethod1());
65 Console.WriteLine(a.Name + " " + a.VirtualMethod2());
66 Console.WriteLine(a.Name + " " + a.Method());
67 Console.ReadKey();
68 }
69 }
70 }
71
抽象类和抽象函数
C#允许把类和函数声明为 abstract 抽象类或抽象函数。抽象类是不能实例化的,只提供给子类继承。抽
象函数则不需提供具体实现的代码,只要声明一个方法名就可以了。抽象类中可以声明非抽象的函数,但
抽象函数只能在抽象类中声明,如在非抽象类中声明抽象函数会导致编译错误。对于抽象类的私有变量可
以在声明时提供初始值。例如:
abstract class MyAbstractClass
{
private int result = 0;
public abstract int AbstractMethod();
public int MyMethod()
{
return result;
}
}
密封类和密封方法来终止继承
当把一个类或函数声明为 sealed 时,那么其他类就不能再继承这个类或者重写这个函数。在编写自己的
框架时可以用这种方法来防止其他用户继承类或重写函数。例如:.NET类库就大量使用了密封类。
例如:
sealed class MyFinalClass
{
// ......
}
注意构造函数的执行顺序。基类的构造函数总是最先调用。也就是说,派生类的构造函数可以在执行过程
中调用基类的方法、属性和其他成员,因为基类已经构造出来的其他字段也已经初始化了。
访问修饰符
对类或类的成员可以使用 public、protected、internal、private 和 protected internal 这些修饰符
定义类或成员的可见性。
public 所有类型或成员 任何代码均可访问
protected 类型和内嵌类型的所有成员 只有派生的类型可以访问
internal 类型和内嵌类型的所有成员 只能在包含它的程序集中访问它
private 所有类型或成员 只能在它所属的类型中访问它
protected internal 类型和内嵌类型的所有成员 只能在包含它的程序集和派生类型的代码中访问
注:在命名空间下的元素不能使用private, protected, 或 protected internal 这些修饰符。只能使用
public 或 internal 来修饰。但C#中类可以嵌套声明,当一个类在另一个类中定义时,可以作为另一个
类的成员被指定protected来声明。如果有嵌套的类,那么内部的类总可以访问外部类的所有成员,甚至
是私有成员。
如果C#中定义的一个类如果没有任何修饰符,那么它默认的访问修饰符是私有的。使用 public 这个修饰
符后无论是类还是方法,均可在任意代码区域访问到。使用 protected 定义的成员,只有继承该类的类
能够访问。使用 internal 定义的成员,能够在当前解决方案(即当前程序集)中有效,也就是该类的实例
对象和继承自该类的代码都可以访问。如果一个成员被定义为 private 那么它就只能在这个类的代码范
围内访问。使用 protected internal 定义的成员,能够在当前解决方案(即当前程序集)中有效。
其他修饰符
new 函数成员 隐藏或重写基类函数
static 所有成员 声明静态成员,不需要实例化就可使用
virsual 类和函数 声明可以被重载的函数
abstract 类和函数 只声明函数,不做具体实现由继承类实现
override 函数成员 重载函数时的显示声明
sealed 类和函数 用于显示声明该类或方法不能再被继承
extern 用于静态[DLLImport]方法 说明在外部实用另一语言实现
接口
一般情况下,接口中只能包含方法、属性、索引器和事件的声明。作为公共契约,接口不能被实例化,只
能包含成员的定义。接口不能有构造函数或字段,也不允许包换运算符重载。除此之外,接口还不允许声
明成员的修饰符,因为接口的成员都是公共的,不需要再声明成为其他类型。也不能将成员声明成虚拟或
静态的。接口可以互相继承就像类一样,如果一个类要实现一个接口,而这个接口又是派生于另一个接口
,那么这个类就需要实现这两个接口所声明的全部成员。
例如:
2 using System.Collections.Generic;
3 using System.Text;
4 using Lesson.Interface;
5 using Lesson.VenusBank;
6 using Lesson.JupiterBank;
7
8 namespace Lesson.Interface
9 {
10 public interface IBankAccount
11 {
12 void PayIn(decimal amount);
13 bool Withdraw(decimal amount);
14 decimal Balance { get; }
15 }
16
17 // 接口的继承
18 public interface ITransferBankAccount : IBankAccount
19 {
20 // 把接口引用当作传递参数传递
21 bool TransferTo(IBankAccount destination, decimal amount);
22 }
23 }
24
25 namespace Lesson.VenusBank
26 {
27 public class SaverAccount : IBankAccount
28 {
29 private decimal balance;
30
31 public void PayIn(decimal amount)
32 {
33 balance += amount;
34 }
35
36 public bool Withdraw(decimal amount)
37 {
38 if (balance >= amount)
39 {
40 balance -= amount;
41 return true;
42 }
43 Console.WriteLine("Withdrawal attempt failed.");
44 return false;
45 }
46
47 public decimal Balance
48 {
49 get { return balance; }
50 }
51
52 public override string ToString()
53 {
54 return String.Format("Venus Bank Saver: Balance = {0,6:C}", balance);
55 }
56 }
57 }
58
59 namespace Lesson.JupiterBank
60 {
61 public class GoldAccount : IBankAccount
62 {
63 private decimal balance;
64
65 public void PayIn(decimal amount)
66 {
67 balance += amount;
68 }
69
70 public bool Withdraw(decimal amount)
71 {
72 if (balance >= amount)
73 {
74 balance -= amount;
75 return true;
76 }
77 Console.WriteLine("Withdrawal attempt failed.");
78 return false;
79 }
80
81 public decimal Balance
82 {
83 get { return balance; }
84 }
85
86 public override string ToString()
87 {
88 return String.Format("Jupiter Bank Saver: Balance = {0,6:C}", balance);
89 }
90 }
91 }
92
93 namespace Lesson04
94 {
95 public class CurrentAccount : ITransferBankAccount
96 {
97 private decimal balance;
98
99 public void PayIn(decimal amount)
100 {
101 balance += amount;
102 }
103
104 public bool Withdraw(decimal amount)
105 {
106 if (balance >= amount)
107 {
108 balance -= amount;
109 return true;
110 }
111 Console.WriteLine("Withdrawal attempt failed.");
112 return false;
113 }
114
115 public decimal Balance
116 {
117 get { return balance; }
118 }
119
120 public override string ToString()
121 {
122 return String.Format("Current Account: Balance = {0,6:C}", balance);
123 }
124
125 // 实现转帐功能
126 public bool TransferTo(IBankAccount destination, decimal amount)
127 {
128 bool result;
129 if ((result = Withdraw(amount)) == true)
130 destination.PayIn(amount);
131 return result;
132 }
133 }
134
135 class Program
136 {
137 static void Main(string[] args)
138 {
139 // 接口引用
140 IBankAccount venusAccount = new SaverAccount();
141 IBankAccount jupiterAccount = new GoldAccount();
142
143 // 存入
144 venusAccount.PayIn(200);
145 Console.WriteLine(venusAccount.ToString());
146
147 // 取出
148 venusAccount.Withdraw(100);
149 Console.WriteLine(venusAccount.ToString());
150
151 // 存入
152 jupiterAccount.PayIn(500);
153 Console.WriteLine(jupiterAccount.ToString());
154
155 // 取出
156 jupiterAccount.Withdraw(600);
157
158 // 取出
159 jupiterAccount.Withdraw(200);
160 Console.WriteLine(jupiterAccount.ToString());
161
162 // 转帐前
163 ITransferBankAccount currentAccount = new CurrentAccount();
164 currentAccount.PayIn(1000);
165 Console.WriteLine(currentAccount.ToString());
166 Console.WriteLine(venusAccount.ToString());
167
168 // 转帐后
169 currentAccount.TransferTo(venusAccount, 500);
170 Console.WriteLine(currentAccount.ToString());
171 Console.WriteLine(venusAccount.ToString());
172
173 Console.ReadKey();
174 }
175 }
176 }
177
178
接口引用类似类的引用,但接口引用最大的好处是可以对所有实现了这个接口的所有类型进行操作。我们
不用担心这个类是来做什么的,只要它实现了这个接口,我们就可以使用接口引用它,来进行接口指定的
操作。
洪虎
2006-10-2