我们前面讲过了类和类之间的继承关系,我们了解到,超类方法和子类方法(或属性)可以具备三种联系方式。
- 继承。超类修饰为public或protected的方法或属性可以被子类继承并访问;
- 虚拟。虚拟的前提是可继承。超类中修饰为virtual关键字的方法或属性可以被子类继承或覆盖;
- 抽象。抽象的前提是可继承。超类中修饰为abstract关键字的方法或属性可以被子类继承并实现,超类方法没有方法体,由子类提供;
现在我们来学习超类方法(或属性)和子类方法(或属性)的第四种联系——隐藏。
子类继承超类方法,并一个和该方法同名、同参数的新方法,子类方法前不使用override关键字修饰。这种情况子类隐藏了超类继承的一个方法。
隐藏和覆盖最大的不同:隐藏的方法只能用该方法所属的类的变量访问,使用超类变量则无法访问,只能访问被隐藏的方法。看例子:
1 | using System; |
2 | using System.Collections.Generic; |
3 | using System.Linq; |
4 | using System.Text; |
5 | |
6 | namespace Edu.Study.OO.Hiden { |
7 | |
8 | /// <summary> |
9 | /// 定义一个球体类 |
10 | /// </summary> |
11 | public class Ball { |
12 | |
13 | /// <summary> |
14 | /// 半径字段 |
15 | /// </summary> |
16 | private double r; |
17 | |
18 | /// <summary> |
19 | /// 构造器,输入球体半径 |
20 | /// </summary> |
21 | public Ball(double r) { |
22 | this.R = r; |
23 | } |
24 | |
25 | /// <summary> |
26 | /// 半径属性 |
27 | /// </summary> |
28 | public double R { |
29 | get { |
30 | return this.r; |
31 | } |
32 | set { |
33 | if (value > 0) { |
34 | this.r = value; |
35 | } else { |
36 | Console.WriteLine("半径长度必须大于0。"); |
37 | } |
38 | } |
39 | } |
40 | |
41 | /// <summary> |
42 | /// 球体面积方法,返回球体面积,该方法虚拟 |
43 | /// </summary> |
44 | public virtual double Area() { |
45 | return 4 * Math.PI * Math.Pow(this.R, 2); |
46 | } |
47 | |
48 | /// <summary> |
49 | /// 球体体积方法,返回球体体积,该方法虚拟 |
50 | /// </summary> |
51 | public virtual double Volume() { |
52 | return (4F / 3F) * Math.PI * Math.Pow(this.R, 3); |
53 | } |
54 | } |
55 | |
56 | |
57 | /// <summary> |
58 | /// 圆柱体类,继承自球体 |
59 | /// </summary> |
60 | public class Cylindrical : Ball { |
61 | |
62 | /// <summary> |
63 | /// 圆柱体高度 |
64 | /// </summary> |
65 | private double height; |
66 | |
67 | /// <summary> |
68 | /// 构造器,输入半径和高度 |
69 | /// </summary> |
70 | public Cylindrical(double r, double height) |
71 | : base(r) { |
72 | this.Height = height; |
73 | } |
74 | |
75 | /// <summary> |
76 | /// 高度属性 |
77 | /// </summary> |
78 | public double Height { |
79 | get { |
80 | return this.height; |
81 | } |
82 | set { |
83 | if (value > 0) { |
84 | this.height = value; |
85 | } else { |
86 | Console.WriteLine("高度必须大于0。"); |
87 | } |
88 | } |
89 | } |
90 | |
91 | /// <summary> |
92 | /// 覆盖超类中虚拟的面积方法,求圆柱体表面积 |
93 | /// </summary> |
94 | public override double Area() { |
95 | return (2 * Math.PI * this.R * this.Height) + (Math.PI * Math.Pow(this.R, 2) * 2); |
96 | } |
97 | |
98 | /// <summary> |
99 | /// 隐藏超类中球体积的方法,改为求圆柱体体积 |
100 | /// </summary> |
101 | public new double Volume() { |
102 | return Math.PI * Math.Pow(this.R, 2) * this.Height; |
103 | } |
104 | } |
105 | |
106 | class Program { |
107 | |
108 | static void Main(string[] args) { |
109 | |
110 | // 输出Ball类对象Area方法和Volume方法的返回值 |
111 | Ball ball = new Ball(20); |
112 | Console.WriteLine("Ball's area = {0}", ball.Area()); |
113 | Console.WriteLine("Ball's area = {0}", ball.Volume()); |
114 | |
115 | // 输出Cylindrical类对象Area方法和Volume方法的返回值 |
116 | Cylindrical cyl = new Cylindrical(20, 30); |
117 | Console.WriteLine("Cylindrical's area = {0}", cyl.Area()); |
118 | Console.WriteLine("Cylindrical's area = {0}", cyl.Volume()); |
119 | |
120 | |
121 | // 令Ball类的变量引用到Cylindrical类的对象上 |
122 | ball = cyl; |
123 | Console.WriteLine("Ball's area = {0}", ball.Area()); // 此处调用Area方法时,调用的其实是Cylindrical类的Area方法 |
124 | Console.WriteLine("Ball's area = {0}", ball.Volume()); // 此处调用Volume方法时,调用的其实是Ball类的方法 |
125 | } |
126 | } |
127 | } |
从代码中可以清楚的看到覆盖和隐藏具体的区别,当使用超类变量引用到子类实例后,依旧可以访问子类覆盖后的方法,但无法访问到子类隐藏后的方法,只能访问被子类隐藏的方法。
注意,本例是为了让大家看明白覆盖和隐藏的区别,对于隐藏来说,被隐藏的超类方法无须修饰为virtual。
一半,子类方法要隐藏父类方法,在方法声明前加上new关键字,表示此方法是被隐藏的方法。new关键字并不是必需的。
隐藏表示了这样一种含义:子类定义了和超类名称相同但流程不同的方法,但只能以子类类型可以访问,使用超类类型则无法访问。
隐藏的情况较之覆盖要少见一些,以至于许多面向对象语言(例如Java)并不提供方法的隐藏。C#提供此特性是为了保证语言的完整性,在一些特殊情况下可以解决一些问题。