首页 > 编程笔记 > C#笔记 阅读:11

C# abstract抽象类的用法(附带实例)

C# 中,使用 abstract 关键词声明的类称为抽象类,在这个类中它可以有抽象方法,也可以有实体方法。

C# 的抽象概念很重要的理念是隐藏工作细节,对于使用者而言,仅需知道如何使用这些功能。例如,“+”符号可以执行数值的加法计算,也可以执行字符串的相加(结合),可是我们不知道内部程序如何设计这个“+”符号的功能。

C#抽象类的应用场景

先看一个程序实例。

【实例 1】有一个 Shape 类内含计算绘制外形的 Draw() 方法,Circle 类和 Rectangle 类则继承 Shape 类,然后执行外形绘制。
Rectangle rectangle = new Rectangle(); // 定义 rectangle
Circle circle = new Circle(); // 定义 circle
rectangle.Draw();
circle.Draw();

public class Shape
{
    public virtual void Draw() { } // 纯定义
}

public class Rectangle : Shape // 定义 Rectangle 类
{
    public override void Draw() // 绘制矩形
    {
        Console.WriteLine("绘制矩形");
    }
}

public class Circle : Shape // 定义 Circle 类
{
    public override void Draw() // 绘制圆形
    {
        Console.WriteLine("绘制圆形");
    }
}
执行结果为:

绘制矩形
绘制圆形

程序中,Shape 类定义了绘制外形的方法 Draw(),但是它不是具体的对象所以无法实际绘制外形,Rectangle 类和 Circle 类继承了 Shape 类,这两个类针对了自己的外形特色重写绘制外形的 Draw() 方法。

由上述可知,Shape 类的存在主要是让整个程序的定义更加完整,它本身不处理任何工作,真正的工作交由子类完成,其实这就是一个适合使用抽象类的场合。

我们扩充上述概念再看一个类似但是稍微复杂的实例。

【实例 2】有一个 Shape 类内含计算面积的 Area() 方法,Circle 类和 Rectangle 类则继承 Shape 类,然后执行面积计算。
Rectangle rectangle = new Rectangle(2, 3); // 定义 rectangle
Circle circle = new Circle(2); // 定义 circle
Console.WriteLine(rectangle.Area());
Console.WriteLine(circle.Area());

public class Shape
{
    public virtual double Area() // 纯定义计算面积
    {
        return 0.0;
    }
}

public class Rectangle : Shape // 定义 Rectangle 类
{
    protected double Height { get; set; } // 高
    protected double Width { get; set; } // 宽
    public Rectangle(double height, double width)
    {
        Height = height;
        Width = width;
    }
    public override double Area() // 计算矩形面积
    {
        return Height * Width;
    }
}

public class Circle : Shape // 定义 Circle 类
{
    protected double R { get; set; } // 半径
    public Circle(double r)
    {
        R = r;
    }
    public override double Area() // 计算圆面积
    {
        return Math.PI * R * R;
    }
}
执行结果为:

6
12.5663706143592

程序中的 Shape 类定义了计算面积的方法 Area(),但是它不是具体的对象所以无法提供实际的计算面积,Rectangle 类和 Circle 类继承了 Shape 类,这两个类针对了自己的外形特色重写计算面积的 Area() 方法。

由上述可知,该 Shape 类的存在主要是让整个程序定义更加完整,它本身也不处理任何工作,真正的工作交由子类完成,这也是一个适合使用抽象类的场合。

C#抽象类的定义

抽象类的定义基本上就是在定义类名称的 class 左边加上 abstract 关键词。

例如:
存取修饰词 abstract class Shape{
    xxx;
}
因为抽象类定义的方法交由子类重新定义,其本身可以想成一个模板,然后由子类依自己的情况对此模板扩展和构造,并由子类对象执行,所以抽象类不能创建对象,若是尝试创建抽象类的对象,则在编译阶段会有错误产生。

【实例 3】:修改【实例 1】,第 2 行尝试创建抽象类对象,产生编译错误的实例。
Shape shape = new Shape(); // 创建抽象对象产生错误

abstract class Shape
{
    abstract public void Draw(); // 纯定义
}

class Rectangle : Shape // 定义 Rectangle 类
{
    public override void Draw() // 绘制矩形
    {
        Console.WriteLine("绘制矩形");
    }
}

class Circle : Shape // 定义 Circle 类
{
    public override void Draw() // 绘制圆形
    {
        Console.WriteLine("绘制圆形");
    }
}
执行结果为:


上述程序错误主要在第 2 行,错误原因是为抽象类 Shape 声明了一个对象。

C#抽象方法

在实例 3 的抽象类中可以看到,程序第 5 行是 Shape 类的 Draw() 方法,这个方法基本上没有执行任何具体工作,存在的主要意义是让未来继承的子类可以重写,对于这种特性的方法我们可以将它定义为抽象方法。

设计抽象方法的基本概念如下:
在定义抽象方法时,需留意回传值类型必须一致并且如果方法内有参数则此参数必须保持。声明抽象方法非常简单,不需定义主体,定义抽象方法的格式如下:
存取修饰词  abstract 方法类型 方法名称();

以实例 3 中 Shape 类的 Draw() 为例,可用下列方式定义抽象方法:
public abstract void draw();

【实例 4】设计第一个正确的抽象类程序,现在我们用抽象类概念,来重新设计实例 1。
Rectangle rectangle = new Rectangle(); // 定义 rectangle
Circle circle = new Circle(); // 定义 circle
rectangle.Draw();
circle.Draw();

abstract class Shape
{
    abstract public void Draw(); // 纯定义
}

class Rectangle : Shape // 定义 Rectangle 类
{
    public override void Draw() // 绘制矩形
    {
        Console.WriteLine("绘制矩形");
    }
}

class Circle : Shape // 定义 Circle 类
{
    public override void Draw() // 绘制圆形
    {
        Console.WriteLine("绘制圆形");
    }
}
执行结果为:

绘制矩形
绘制圆形

读者需要学会第 6~9 行抽象类的声明方式,还要学会第 8 行抽象方法的声明方式。

在设计抽象方法时,必须留意回传值类型,详情可参考下列实例。

【实例 5】用抽象类与抽象方法重新设计方案实例 2,下列只列出了 Shape 类的设计,其他程序代码则与实例 2 完全相同。
public abstract class Shape
{
    public abstract double Area(); // 纯定义计算面积
}
执行结果与实例 2 相同。

上述程序的重点是必须保持抽象方法的回传值类型一致,此例是 double。

C#抽象类和抽象方法概念整理

根据以上讲解的内容,笔者将抽象类与抽象方法概念整理如下:
【实例 6】抽象类可以有抽象方法和普通方法的实例应用。
Bmw bmw = new Bmw();
bmw.Refuel();
bmw.Run();

abstract class Car
{
    public abstract void Run(); // 抽象方法
    public void Refuel() // 一般方法
    {
        Console.WriteLine("汽车加油");
    }
}

class Bmw : Car
{
    public override void Run() // 重写 Run 方法
    {
        Console.WriteLine("安全驾驶中...");
    }
}
执行结果为:

汽车加油
安全驾驶中...

程序中,Car 是一个抽象类,此类定义了抽象方法 Run() 和普通方法 Refuel(),程序第 2 行和第 3 行分别调用这两个方法,结果可以正常执行。

【实例 7】重新设计实例 6,将 Bmw 也设为抽象类,此抽象类有一般方法 Color() 可以输出车身颜色,然后底下再增设 Type750 孙类,由此孙类完成对抽象方法 Run() 的重写。
Bmw bmw = new Type750();
bmw.Refuel();
bmw.Color();
bmw.Run();

abstract class Car // 抽象类
{
    public abstract void Run(); // 抽象方法
    public void Refuel() // 一般方法
    {
        Console.WriteLine("汽车加油");
    }
}

abstract class Bmw : Car // 抽象类继承 Car
{
    public void Color() // 一般方法
    {
        Console.WriteLine("车身是银灰色");
    }
}

class Type750 : Bmw // 抽象类继承 Bmw
{
    public override void Run() // 重写 Run 方法
    {
        Console.WriteLine("安全驾驶中...");
    }
}
执行结果为:

汽车加油
车身是银灰色
安全驾驶中...

C#抽象类的构造方法

设计 C# 程序时也可将构造方法或属性(成员变量)的概念应用在抽象类中。

【实例 8】增加构造方法重新设计实例 6。
Bmw bmw = new Bmw();
bmw.Refuel();
bmw.Run();

abstract class Car
{
    public Car() // 构造方法
    {
        Console.WriteLine("有车子了");
    }
    public abstract void Run(); // 抽象方法
    public void Refuel() // 一般方法
    {
        Console.WriteLine("汽车加油");
    }
}

class Bmw : Car
{
    public override void Run() // 重写 Run 方法
    {
        Console.WriteLine("安全驾驶中...");
    }
}
执行结果为:

有车子了
汽车加油
安全驾驶中...

上述程序执行第 1 行创建 Bmw 类对象 bmw 时,会执行构造方法,第 1 行的输出“有车子了”就是构造方法的输出。

适度修改上面的实例,执行属性(property)的重写:
Bmw bmw = new Bmw("Peter");
bmw.Refuel();
bmw.Run();

public abstract class Car
{
    public abstract string Name { get; } // 抽象属性,相当于 readonly
    public abstract void Run(); // 抽象方法
    public void Refuel()
    {
        Console.WriteLine($"{Name} 汽车加油"); // 一般方法
    }
}

public class Bmw : Car
{
    private string name; // 定义姓名
    public Bmw(string _name) // 构造方法
    {
        this.name = _name;
    }
    public override void Run() // 重写 Run 方法
    {
        Console.WriteLine("安全驾驶中...");
    }
    public override string Name // 重写 Name 属性
    {
        get { return this.name + "旅行"; }
    }
}
执行结果为:

Peter旅行 汽车加油
安全驾驶中...

上述第 7 行和第 8 行定义了抽象属性,因为不想更改所以只有 get,这相当于是只读 read-only。

继承的子类 Bmw 有 name 字段,实际重写 Name 属性时使用第 26~第 29 行,增加“旅行”字符串。所以第 12 行的输出可以先看到“Peter旅行”字符串。

运行时多态应用到抽象类中

我们无法为抽象类声明对象,但是可以使用 C# 中的向上转型概念,即使用抽象类声明对象指向子类对象,由于所声明的对象的引用指向子类的对象,所以可以正常执行工作,其实这就是多态的概念。

其实现在常常可以看到有些 C# 程序设计师在使用这个概念执行抽象类对象的声明。

例如,使用向上转型的概念重新设计实例 6:
Car bmw = new Bmw();
执行结果和实例 6 相同

相关文章