目录
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
静态多态性
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
动态多态性
是通过 抽象类 和 虚方法 实现的。
使用多态的好处
1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。
2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
在同一个类中。允许同名方法。他们的参数个数,或参数类型不同即可。
函数的重载在平常用的还是比较多的,下面代码中两个 Add 方法,就属于函数的重载
- public class Compute
- {
- public int Add(int x,int y)
- {
- return x + y;
- }
-
- public float Add(float x,float y)
- {
- return x + y;
- }
- }
用户定义的类型可重载预定义的 C# 运算符。 也就是说,当一个或两个操作数都是某类型时,此类型可提供操作的自定义实现。 可重载运算符部分介绍了哪些 C# 运算符可重载。
使用 operator 关键字来声明运算符。 运算符声明必须符合以下规则:
public 和 static 修饰符。T 或 T?,其中 T 是包含运算符声明的类型。参考:
运算符重载 - C# 引用 | Microsoft Learn
| 运算符 | 限制 |
|---|---|
| +x -x !x ~x ++ -- true false | 无。 |
| x + y x - y x * y x / y x % y x & y x | y x ^ y x << y x >> y | 无。 |
| x == y x != y x < y x > y x <= y x >= y | 必须成对重载:== and !=< and ><= and >=。 |
| 运算符 | 备选方法 |
|---|---|
| x && y x || y | 重载 true 和 false 运算符以及 & 或 | 运算符。 有关详细信息,请参阅用户定义的条件逻辑运算符。 |
| a[i] a?[i] | 定义索引器。 |
| (T)x | 定义可由强制转换表达式执行的自定义类型转换。 有关详细信息,请参阅用户定义转换运算符。 |
| += -= \*= /= %= &= |= ^= <<= >>= | 重载相应的二元运算符。 例如,重载 + 也会隐式重载 +=。 |
| ^x x = y x.y x?.y c ? t : f x ?? y ??= y x..y x->y => f(x) as await checked unchecked default delegate is nameof new sizeof stackalloc switch typeof with | 无。 |
看到这里,估计很多人会问,这是什么一堆乱七八糟的东西,说了半天,运算符的重载到底是什么?
下面代码,你可能会用过
- Version v1 = new Version(txt1.Text);
- Version v2 = new Version(txt2.Text);
- if (v1 > v2)
- {
- MessageBox.Show("版本1高于版本2");
- }
- if (v1 < v2)
- {
- MessageBox.Show("版本1低于版本2");
- }
那么,运算符的重载在哪?看到 if(v1 > v2) 这句没有,这里的 “>” 就是运算符的重载,v1 和 v2 明明是两个类,这个是怎么用运算符进行运算的?那么下面就介绍运算符的重载到底是怎么运用的。
- using System;
-
- namespace Test1
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- MyPoint myPoint1 = new MyPoint();
- myPoint1.X = 2;
- myPoint1.Y = 3;
- MyPoint myPoint2 = new MyPoint();
- myPoint2.X = 4;
- myPoint2.Y = 5;
-
- MyPoint myPoint3 = myPoint1 + myPoint2;
- Console.WriteLine("X:" + myPoint3.X);
- Console.WriteLine("Y:" + myPoint3.Y);
-
- Console.ReadKey();
- }
- }
-
- public class MyPoint
- {
- public int X { get; set; }
- public int Y { get; set; }
-
- public static MyPoint operator +(MyPoint p1, MyPoint p2)
- {
- MyPoint myPoint = new MyPoint();
- myPoint.X = p1.X + p2.X;
- myPoint.Y = p1.Y + p2.Y;
- return myPoint;
- }
- }
- }
运行:
X:6
Y:8
这里的 X,Y 的结果,刚好是 myPoint1 和 myPoint2 相加的结果。
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法,虚方法是使用关键字 virtual 声明的,虚方法可以在不同的继承类中有不同的实现,即为基类中定义的允许在派生类中重写的方法。
注意:
1.因为虚方法需要被子类调用,所以访问修饰符不能为private
2.父类虚方法使用的什么访问修饰符,子类重写就必须用什么访问修饰符
案例
- using System;
-
- namespace Test1
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- Bird bird = new Sparrow();
- bird.Eat();
- bird = new Eagle();
- bird.Eat();
-
- Console.ReadKey();
- }
- }
-
- public class Bird
- {
- //吃
- public virtual void Eat()
- {
- Console.WriteLine("Bird-Eat");
- }
- }
-
- //麻雀
- public class Sparrow : Bird
- {
- public override void Eat()
- {
- Console.WriteLine("麻雀吃稻谷");
- }
- }
-
- //老鹰
- public class Eagle : Bird
- {
- public override void Eat()
- {
- Console.WriteLine("老鹰吃坤坤");
- }
- }
-
- }
运行:
麻雀吃稻谷
老鹰吃坤坤
abstract 修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract 修饰符来指示某个类仅用作其他类的基类,而不用于自行进行实例化。 标记为抽象的成员必须由派生自抽象类的非抽象类来实现。
抽象类具有以下功能:
抽象类不能实例化。
抽象类可能包含抽象方法和访问器。
无法使用 sealed 修饰符来修改抽象类,因为两个修饰符的含义相反。 sealed 修饰符阻止类被继承,而 abstract 修饰符要求类被继承。
派生自抽象类的非抽象类,必须包含全部已继承的抽象方法和访问器的实际实现。
在方法或属性声明中使用 abstract 修饰符,以指示该方法或属性不包含实现。
抽象方法具有以下功能:
抽象方法是隐式的虚拟方法。
只有抽象类中才允许抽象方法声明。
由于抽象方法声明不提供实际的实现,因此没有方法主体;方法声明仅以分号结尾,且签名后没有大括号 ({ })。 例如:
public abstract void MyMethod();
实现由方法 override 提供,它是非抽象类的成员。
除了声明和调用语法方面不同外,抽象属性的行为与抽象方法相似。
在静态属性上使用 abstract 修饰符是错误的。
通过包含使用 override 修饰符的属性声明,可在派生类中重写抽象继承属性。
抽象类和抽象方法在设计模式和框架中用的特别多。
案例
- using System;
-
- namespace Test1
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- Bird bird = new Sparrow();
- bird.Eat();
- bird = new Eagle();
- bird.Eat();
-
- Console.ReadKey();
- }
- }
-
- public abstract class Bird
- {
- //吃
- public abstract void Eat();
-
- //在抽象类中使用普通的方法和字段一样没问题
- public void Birdsong()
- {
- Console.WriteLine("鸟叫");
- }
- public int Age { get; set; }
- }
-
- //麻雀
- public class Sparrow : Bird
- {
- public override void Eat()
- {
- Console.WriteLine("麻雀吃稻谷");
- }
- }
-
- //老鹰
- public class Eagle : Bird
- {
- public override void Eat()
- {
- Console.WriteLine("老鹰吃坤坤");
- }
- }
-
- }
运行:
麻雀吃稻谷
老鹰吃坤坤
接口的使用方法,也可以和多态一样的使用,这就是我为什么在多态的文章中加入了接口,下面是微软官方的一些介绍。
接口可以是命名空间或类的成员。 接口声明可以包含以下成员的声明(没有任何实现的签名):
默认接口成员
上述成员声明通常不包含主体。 从 C# 8.0 开始,接口成员可以声明主体。 接口中的成员主体是默认实现。 具有主体的成员允许接口为不提供重写实现的类和结构提供“默认”实现。 此外,从 C# 8.0 开始,接口可以包括:
接口的相关内容比较多,可以参考微软的官方文档
在使用上,和上面的虚方法,抽象方法 用起来也差不多,区别是,接口可以继承多个,但子类不能继承多个父类
案例
- using System;
-
- namespace Test1
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- Bird bird = new Sparrow();
- bird.Eat();
- bird = new Eagle();
- bird.Eat();
-
- Console.ReadKey();
- }
- }
-
- public interface Bird
- {
- //吃
- void Eat();
- }
-
- //麻雀
- public class Sparrow : Bird
- {
- public void Eat()
- {
- Console.WriteLine("麻雀吃稻谷");
- }
- }
-
- //老鹰
- public class Eagle : Bird
- {
- public void Eat()
- {
- Console.WriteLine("老鹰吃坤坤");
- }
- }
-
- }
运行:
麻雀吃稻谷
老鹰吃坤坤
如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢
end