目录

一、简介

二、函数的重载

三、运算符的重载

可重载运算符

不可重载运算符

运算符重载案例

四、动态多态性

1.虚方法

2.抽象类和抽象方法

五、interface

结束


一、简介

多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

静态多态性

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。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 相加的结果。

四、动态多态性

1.虚方法

当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法,虚方法是使用关键字 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("老鹰吃坤坤");
        }
    }

}

运行:

麻雀吃稻谷
老鹰吃坤坤

2.抽象类和抽象方法

abstract 修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract 修饰符来指示某个类仅用作其他类的基类,而不用于自行进行实例化。 标记为抽象的成员必须由派生自抽象类的非抽象类来实现。

抽象类具有以下功能

  • 抽象类不能实例化。

  • 抽象类可能包含抽象方法和访问器。

  • 无法使用 sealed 修饰符来修改抽象类,因为两个修饰符的含义相反。 sealed 修饰符阻止类被继承,而 abstract 修饰符要求类被继承。

  • 派生自抽象类的非抽象类,必须包含全部已继承的抽象方法和访问器的实际实现。

在方法或属性声明中使用 abstract 修饰符,以指示该方法或属性不包含实现。

抽象方法具有以下功能

  • 抽象方法是隐式的虚拟方法。

  • 只有抽象类中才允许抽象方法声明。

  • 由于抽象方法声明不提供实际的实现,因此没有方法主体;方法声明仅以分号结尾,且签名后没有大括号 ({ })。 例如:

    public abstract void MyMethod();  
    

    实现由方法 override 提供,它是非抽象类的成员。

  • 在抽象方法声明中使用 static 或 virtual 修饰符是错误的。

除了声明和调用语法方面不同外,抽象属性的行为与抽象方法相似。

  • 在静态属性上使用 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("老鹰吃坤坤");
        }
    }

}

运行:

麻雀吃稻谷
老鹰吃坤坤

五、interface

接口的使用方法,也可以和多态一样的使用,这就是我为什么在多态的文章中加入了接口,下面是微软官方的一些介绍。

接口可以是命名空间或类的成员。 接口声明可以包含以下成员的声明(没有任何实现的签名):

默认接口成员

上述成员声明通常不包含主体。 从 C# 8.0 开始,接口成员可以声明主体。 接口中的成员主体是默认实现。 具有主体的成员允许接口为不提供重写实现的类和结构提供“默认”实现。 此外,从 C# 8.0 开始,接口可以包括:

接口的相关内容比较多,可以参考微软的官方文档

接口 - C# 参考 | Microsoft Learn

在使用上,和上面的虚方法,抽象方法 用起来也差不多,区别是,接口可以继承多个,但子类不能继承多个父类

案例

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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐