目录
C# 多态性
多态是同一个行为具有多个不同表现形式或形态的能力。
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
现实中,比如我们按下 F1 键这个动作:
- 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
- 如果当前在 Word 下弹出的就是 Word 帮助;
- 在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果。
静态多态性
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
函数重载
在C#中,函数重载指的是在同一个类中可以定义多个具有相同名称但参数列表不同的函数。通过函数重载,可以根据不同的参数列表来调用合适的函数。
下面是一个简单的示例,演示了如何在C#中使用函数重载:
using System;
public class DateUtils
{
// 函数重载
public string FormatDate(DateTime dt)
{
return dt.ToString("yyyy/MM/dd HH:mm:ss.fff");
}
public string FormatDate(DateTime dt, string format)
{
return dt.ToString(format);
}
}
public class Program
{
public static void Main()
{
DateUtils utils = new DateUtils();
DateTime now = DateTime.Now;
// 使用不同的重载函数进行日期格式化
string formattedDate1 = utils.FormatDate(now); // 使用第一个重载函数
string formattedDate2 = utils.FormatDate(now, "yyyy-MM-dd HH:mm:ss"); // 使用第二个重载函数
// 输出格式化后的日期
Console.WriteLine("格式化日期1: " + formattedDate1);
Console.WriteLine("格式化日期2: " + formattedDate2);
}
}
在上面的示例中,DateUtils 类中定义了两个名为 FormatDate 的函数。第一个函数接受一个 DateTime 类型的参数,并将日期格式化为特定的格式。第二个函数接受一个 DateTime 类型的参数和一个表示日期格式的字符串参数,并根据传入的格式对日期进行格式化。这两个函数拥有相同的名称但参数列表不同。
运算符重载
C#允许我们重定义或重载内置运算符,以便在用户自定义类型上使用它们。运算符重载通过关键字operator后面跟着运算符的符号来定义。
下面是一些常见的运算符重载的示例:
1、算术运算符:
- +:重载为public static T operator +(T operand1, T operand2)
- -:重载为public static T operator -(T operand1, T operand2)
- *:重载为public static T operator *(T operand1, T operand2)
- /:重载为public static T operator /(T operand1, T operand2)
- %:重载为public static T operator %(T operand1, T operand2)
2、比较运算符:
- ==:重载为public static bool operator ==(T operand1, T operand2)
- !=:重载为public static bool operator !=(T operand1, T operand2)
- <:重载为public static bool operator <(T operand1, T operand2)
- >:重载为public static bool operator >(T operand1, T operand2)
- <=:重载为public static bool operator <=(T operand1, T operand2)
- >=:重载为public static bool operator >=(T operand1, T operand2)
3、逻辑运算符:
- !:重载为public static bool operator !(T operand)
- &:重载为public static bool operator &(T operand1, T operand2)
- |:重载为public static bool operator |(T operand1, T operand2)
4、位运算符:
- ~:重载为public static T operator ~(T operand)
- &:重载为public static T operator &(T operand1, T operand2)
- |:重载为public static T operator |(T operand1, T operand2)
- ^:重载为public static T operator ^(T operand1, T operand2)
5、赋值运算符:
- =:重载为public static void operator =(T destination, T source)
- +=:重载为public static T operator +=(T operand1, T operand2)
- -=:重载为public static T operator -=(T operand1, T operand2)
- *=:重载为public static T operator *=(T operand1, T operand2)
- /=:重载为public static T operator /=(T operand1, T operand2)
- %=:重载为public static T operator %=(T operand1, T operand2)
这些示例只是一些常见的运算符重载示例,你可以根据自己的需求重载其他运算符。需要注意的是,并非所有运算符都可以被重载,具体可重载的运算符列表请参考C#官方文档。
代码示例
using System;
public class Complex
{
public double Real { get; set; }
public double Imaginary { get; set; }
public Complex(double real, double imaginary)
{
Real = real;
Imaginary = imaginary;
}
// 重载加法运算符 "+"
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
}
// 重载减法运算符 "-"
public static Complex operator -(Complex c1, Complex c2)
{
return new Complex(c1.Real - c2.Real, c1.Imaginary - c2.Imaginary);
}
// 重载乘法运算符 "*"
public static Complex operator *(Complex c1, Complex c2)
{
double real = c1.Real * c2.Real - c1.Imaginary * c2.Imaginary;
double imaginary = c1.Real * c2.Imaginary + c1.Imaginary * c2.Real;
return new Complex(real, imaginary);
}
// 重载复数的输出格式
public override string ToString()
{
return $"{Real} + {Imaginary}i";
}
}
class Program
{
static void Main()
{
Complex c1 = new Complex(1, 2);
Complex c2 = new Complex(3, 4);
Complex sum = c1 + c2; // 使用重载的加法运算符
Console.WriteLine($"总和: {sum}");
Complex difference = c1 - c2; // 使用重载的减法运算符
Console.WriteLine($"差别: {difference}");
Complex product = c1 * c2; // 使用重载的乘法运算符
Console.WriteLine($"相乘: {product}");
}
}
当上面的代码被编译和执行时,它会产生下列结果:
总和: 4 + 6i
差别: -2 + -2i
相乘: -5 + 10i
动态多态性
在C#中,使用抽象类和虚方法可以实现动态多态性。
C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
- 抽象类不能被实例化,只能被用作其他类的基类。
- 抽象类中可以包含抽象方法,这些方法没有具体的实现,而是由派生类来实现。
- 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为 sealed。
下面的程序演示了一个抽象类:
using System;
// 定义抽象类
public abstract class Shape
{
public string Name { get; set; }
// 定义抽象方法
public abstract double CalculateArea();
}
// 继承自抽象类的具体类:圆形
public class Circle : Shape
{
public double Radius { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
// 继承自抽象类的具体类:正方形
public class Square : Shape
{
public double SideLength { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return SideLength * SideLength;
}
}
class Program
{
static void Main()
{
Circle circle = new Circle { Name = "圆形", Radius = 3 };
Console.WriteLine("圆形的面积: " + circle.CalculateArea()); // 输出结果:"圆形的面积: 28.274333882308138"
Square square = new Square { Name = "正方形", SideLength = 4 };
Console.WriteLine("正方形的面积: " + square.CalculateArea()); // 输出结果:"正方形的面积: 16"
}
}
另外,虚方法是在基类中,可以使用关键字virtual来声明一个方法为虚方法,表示它可以被派生类重写。而在派生类中,可以使用关键字override来重写基类中的虚方法。
通过使用抽象类和虚方法,可以实现代码的可扩展性和灵活性。抽象类提供了一种统一的接口,定义了需要实现的方法,而具体的实现则由派生类来完成。虚方法允许在派生类中重写方法,实现不同的行为。
以下是一个简单的代码示例,演示了如何使用抽象类和虚方法来实现动态多态性。
using System;
// 定义一个抽象类
public abstract class Shape
{
// 定义一个虚方法
public virtual double CalculateArea()
{
return 0;
}
}
// 派生类1:矩形
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Width * Height;
}
}
// 派生类2:圆形
public class Circle : Shape
{
public double Radius { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
class Program
{
static void Main()
{
// 创建一个矩形对象
Shape rect = new Rectangle { Width = 5, Height = 10 };
Console.WriteLine("矩形的面积:" + rect.CalculateArea());
// 创建一个圆形对象
Shape circle = new Circle { Radius = 3 };
Console.WriteLine("圆形的面积:" + circle.CalculateArea());
}
}
当上面的代码被编译和执行时,它会产生下列结果:
矩形的面积:50
圆形的面积:28.274333882308138
virtual 和 abstract
- virtual关键字用于修饰方法,表示该方法在基类中有一个默认实现,但派生类可以选择性地重写它。通常情况下,virtual方法会提供一个默认实现,但可以在派生类中进行修改或扩展。即使派生类没有重写virtual方法,它仍然会使用基类中的默认实现。
- abstract关键字用于修饰方法或类,表示它们没有具体的实现。抽象方法只有定义,没有实现,派生类必须重写它们,并且提供具体的实现。抽象类本身也不能被实例化,只能作为基类被其他类继承。
需要注意的是,抽象方法只能存在于抽象类中,而不是普通的类。如果一个类包含抽象方法,则该类本身必须被声明为抽象类。而虚方法可以存在于任何类中,无论是抽象类还是普通类。
总之,virtual和abstract关键字都用于实现多态性,在派生类中重新定义父类的方法。virtual方法允许派生类选择重写并提供新的实现,而abstract方法则要求派生类必须提供具体的实现。另外,抽象类本身不能被实例化,只能作为其他类的基类使用。
抽象方法和虚方法的区别
- 抽象方法(abstract method)必须在抽象类中声明,并且没有具体的实现部分。派生类必须重写并提供具体的实现,否则派生类将无法被实例化。
- 虚方法(virtual method)在基类中有一个默认的实现,但也可以在派生类中进行重写。派生类可以选择性地覆盖虚方法,或者直接使用基类的默认实现。
- 抽象方法和虚方法都可以供派生类重写以定制化行为,但抽象方法必须在派生类中进行重写,而虚方法可以选择性地进行重写。
重载(overload)和重写(override)
1、重载(overload): 在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载,它们有三个特点(俗称两必须一可以):
- 方法名必须相同
- 参数列表必须不相同
- 返回值类型可以不相同
2、重写(override):子类中为满足自己的需要来重复定义某个方法的不同实现,需要用 override 关键字,被重写的方法必须是虚方法,用的是 virtual 关键字。它的特点是(三个相同):
- 相同的方法名
- 相同的参数列表
- 相同的返回值
代码示例
using System;
public class Calculator
{
// 方法重载,参数列表不同
public int Add(int num1, int num2)
{
return num1 + num2;
}
public double Add(double num1, double num2)
{
return num1 + num2;
}
}
public class Shape
{
// 虚方法
public virtual void Draw()
{
Console.WriteLine("正在绘制形状。。。");
}
}
public class Circle : Shape
{
// 重写基类的虚方法
public override void Draw()
{
Console.WriteLine("正在绘制圆。。。");
}
}
class Program
{
static void Main()
{
Calculator calculator = new Calculator();
int result1 = calculator.Add(3, 4); // 调用第一个Add方法
double result2 = calculator.Add(2.5, 3.7); // 调用第二个Add方法
Console.WriteLine("int加法的结果: " + result1); // 输出结果:"int加法的结果: 7"
Console.WriteLine("double加法的结果: " + result2); // 输出结果:"double加法的结果: 6.2"
Shape shape1 = new Shape();
shape1.Draw(); // 输出结果:"正在绘制形状。。。"
Shape shape2 = new Circle();
shape2.Draw(); // 输出结果:"正在绘制圆。。。"
}
}
隐藏方法
当在派生类中定义一个与基类中同名的方法时,会发生方法隐藏。使用关键字 new 可以实现方法隐藏。
using System;
public class Shape
{
public void Draw()
{
Console.WriteLine("正在绘制形状。。。");
}
}
public class Circle : Shape
{
public new void Draw()
{
Console.WriteLine("正在绘制圆。。。");
}
}
class Program
{
static void Main()
{
Shape shape1 = new Shape();
shape1.Draw(); // 输出结果:"正在绘制形状。。。"
Shape shape2 = new Circle();
shape2.Draw(); // 输出结果:"正在绘制形状。。。"(调用的是基类的方法)
Circle circle = new Circle();
circle.Draw(); // 输出结果:"正在绘制圆。。。"
}
}
在上述代码中,Shape 类定义了一个 Draw() 方法,Circle 类继承自 Shape 并定义了一个同名的 Draw() 方法并使用 new 关键字进行方法隐藏。
在 Main() 方法中,首先创建了一个 Shape 对象 shape1,调用其 Draw() 方法,输出结果为 "正在绘制形状。。。"。接着创建了一个 Circle 对象 shape2,将其赋值给 Shape 类型的变量 shape2,再次调用 shape2.Draw() 方法,输出结果仍然为 ""正在绘制形状。。。"",这是因为变量的静态类型是 Shape,所以调用的是基类的方法。最后,创建了一个 Circle 对象 circle,直接调用其 Draw() 方法,输出结果为 "正在绘制圆。。。",这是因为直接通过派生类的实例调用方法时,会调用派生类中隐藏的方法。
需要注意的是,方法隐藏并不是方法重写。如果想要实现方法的多态性,应该使用方法重写(override)而不是隐藏(new)。