404 Not Found

404 Not Found


nginx

抽象类与接口

抽象类与抽象方法

抽象类使用 abstract 修饰,不能被实例化,可包含抽象方法和具体方法。抽象方法没有方法体,必须在派生类中重写。

示例

CSHARP
abstract class Shape
{
    public string Name { get; set; }

    public abstract double Area();

    public void Print()
    {
        Console.WriteLine($"{Name} 面积: {Area():F2}");
    }
}

class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Name = "圆形";
        Radius = radius;
    }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }
}

class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double w, double h)
    {
        Name = "矩形";
        Width = w;
        Height = h;
    }

    public override double Area()
    {
        return Width * Height;
    }
}

Circle c = new Circle(3);
Rectangle r = new Rectangle(4, 5);
c.Print();
r.Print();
▶ 试一试
TEXT
圆形 面积: 28.27
矩形 面积: 20.00

💡 抽象类就像一个"半成品模板",规定了子类必须完成的部分(抽象方法),也提供了可以直接使用的部分(具体方法)。

抽象类与普通类的区别

特性 普通类 抽象类
实例化 可以 不可以
抽象方法 不可以包含 可以包含
具体方法 可以 可以
构造函数
字段
被继承 可选重写virtual 必须重写abstract
sealed修饰 可以 不可以(矛盾)

示例

CSHARP
abstract class Logger
{
    protected string prefix;

    public Logger(string prefix)
    {
        this.prefix = prefix;
    }

    public abstract void Log(string message);

    public void LogWithTimestamp(string message)
    {
        string ts = DateTime.Now.ToString("HH:mm:ss");
        Log($"[{ts}] {message}");
    }
}

class FileLogger : Logger
{
    public FileLogger() : base("FILE") { }

    public override void Log(string message)
    {
        Console.WriteLine($"{prefix}: {message}");
    }
}

FileLogger fl = new FileLogger();
fl.Log("启动服务");
fl.LogWithTimestamp("连接数据库");
▶ 试一试
TEXT
FILE: 启动服务
FILE: [14:30:00] 连接数据库

⚠️ 抽象类可以有构造函数,但构造函数不能是 abstract 的。构造函数在子类实例化时通过 base() 调用。

接口定义与实现

接口使用 interface 关键字定义,是一组成员的契约。实现接口的类必须提供所有成员的实现。接口成员默认为 public,不能有访问修饰符。

示例

CSHARP
interface IMovable
{
    void Move(double dx, double dy);
    double Speed { get; set; }
}

interface IDrawable
{
    void Draw();
}

class Player : IMovable, IDrawable
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Speed { get; set; }

    public Player(double x, double y, double speed)
    {
        X = x;
        Y = y;
        Speed = speed;
    }

    public void Move(double dx, double dy)
    {
        X += dx * Speed;
        Y += dy * Speed;
        Console.WriteLine($"移动到 ({X:F1}, {Y:F1})");
    }

    public void Draw()
    {
        Console.WriteLine($"绘制玩家 @ ({X:F1}, {Y:F1})");
    }
}

Player p = new Player(0, 0, 2.0);
p.Draw();
p.Move(3, 4);
p.Draw();
▶ 试一试
TEXT
绘制玩家 @ (0.0, 0.0)
移动到 (6.0, 8.0)
绘制玩家 @ (6.0, 8.0)

📌 接口名称推荐以大写字母 I 开头,如 IComparableIDisposable,这是 C# 的命名约定。

多接口实现

一个类可以实现多个接口,用逗号分隔。这是 C# 实现多态的方式之一,弥补了单继承的限制。

示例

CSHARP
interface IWalkable
{
    void Walk();
}

interface ISwimmable
{
    void Swim();
}

interface IFlyable
{
    void Fly();
}

class Duck : IWalkable, ISwimmable, IFlyable
{
    public string Name { get; set; }

    public Duck(string name)
    {
        Name = name;
    }

    public void Walk()
    {
        Console.WriteLine($"{Name} 摇摇摆摆地走");
    }

    public void Swim()
    {
        Console.WriteLine($"{Name} 在水里游");
    }

    public void Fly()
    {
        Console.WriteLine($"{Name} 扑腾翅膀飞起来");
    }
}

Duck d = new Duck("唐老鸭");
d.Walk();
d.Swim();
d.Fly();

IWalkable walker = d;
walker.Walk();

ISwimmable swimmer = d;
swimmer.Swim();
▶ 试一试
TEXT
唐老鸭 摇摇摆摆地走
唐老鸭 在水里游
唐老鸭 扑腾翅膀飞起来
唐老鸭 摇摇摆摆地走
唐老鸭 在水里游

💡 不同接口类型的变量只能调用该接口定义的成员,这是接口隔离原则的体现。

接口默认方法(C# 8)

从 C# 8 开始,接口可以包含默认实现的方法。这允许在不破坏现有实现类的情况下向接口添加新成员。

示例

CSHARP
interface ILogger
{
    void Log(string message);

    void LogWarning(string message)
    {
        Log($"[WARN] {message}");
    }

    void LogError(string message)
    {
        Log($"[ERROR] {message}");
    }
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

class SimpleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($">> {message}");
    }
}

ConsoleLogger cl = new ConsoleLogger();
cl.Log("系统启动");
cl.LogWarning("内存不足");
cl.LogError("连接失败");

SimpleLogger sl = new SimpleLogger();
sl.Log("系统启动");
sl.LogWarning("内存不足");
▶ 试一试
TEXT
系统启动
[WARN] 内存不足
[ERROR] 连接失败
>> 系统启动
>> [WARN] 内存不足

⚠️ 默认方法仅在通过接口类型调用时可用,通过实现类类型的变量无法调用默认方法(除非实现类也重新实现该方法)。

抽象类与接口选择指南

对比项 抽象类 接口
继承/实现 单继承 多实现
成员类型 任意 方法、属性、事件、索引器
字段 可以 不可以(C# 8前)
构造函数 可以 不可以
访问修饰符 任意 默认public
默认实现 可以 C# 8起可以
版本兼容 加新成员安全 加新成员破坏实现类(C# 8前)
设计意图 "是什么"(IS-A) "能做什么"(CAN-DO)

选择建议:

🔥 简单口诀:共享身份用抽象,共享能力用接口。

显式接口实现

当多个接口有同名成员,或需要隐藏接口成员时,使用显式接口实现。语法为 返回类型 接口名.成员名,显式实现的成员不能加访问修饰符,且只能通过接口类型调用。

示例

CSHARP
interface IEnglish
{
    void Speak();
}

interface IFrench
{
    void Speak();
}

class BilingualPerson : IEnglish, IFrench
{
    void IEnglish.Speak()
    {
        Console.WriteLine("Hello!");
    }

    void IFrench.Speak()
    {
        Console.WriteLine("Bonjour!");
    }

    public void Speak()
    {
        Console.WriteLine("Hi!");
    }
}

BilingualPerson p = new BilingualPerson();
p.Speak();

IEnglish eng = p;
eng.Speak();

IFrench fr = p;
fr.Speak();
▶ 试一试
TEXT
Hi!
Hello!
Bonjour!

📌 显式接口实现的成员在类实例上不可见,必须转型为对应接口类型才能调用。这既是限制,也是一种封装手段。

IComparable<T> 实战

IComparable<T> 是 .NET 中最常用的接口之一,用于定义类型的自然排序顺序。Array.SortList<T>.Sort 都依赖此接口。

示例

CSHARP
class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public Student(string name, int score)
    {
        Name = name;
        Score = score;
    }

    public int CompareTo(Student other)
    {
        if (other == null) return 1;
        return Score.CompareTo(other.Score);
    }

    public override string ToString()
    {
        return $"{Name}({Score})";
    }
}

Student[] students = new Student[]
{
    new Student("张三", 85),
    new Student("李四", 92),
    new Student("王五", 78),
    new Student("赵六", 95),
    new Student("钱七", 88)
};

Console.WriteLine("排序前:");
foreach (var s in students)
    Console.WriteLine(s);

Array.Sort(students);

Console.WriteLine("排序后(按分数升序):");
foreach (var s in students)
    Console.WriteLine(s);
▶ 试一试
TEXT
排序前:
张三(85)
李四(92)
王五(78)
赵六(95)
钱七(88)
排序后(按分数升序):
王五(78)
张三(85)
钱七(88)
李四(92)
赵六(95)

💻 CompareTo 返回值约定:负数表示当前对象排在前面,零表示相等,正数表示当前对象排在后面。借助 IComparable<T>,自定义类型可以直接使用 Array.SortList<T>.Sort 等排序功能。

❓ 常见问题

Q 抽象类能实现接口吗?
A 能。抽象类可以实现接口,并可以选择实现具体成员或将接口成员保留为抽象。
Q 接口可以继承接口吗?
A 可以。接口可以继承多个接口,实现类必须实现所有继承链上的成员。
Q 显式接口实现能加 public 修饰符吗?
A 不能。显式接口实现不能有任何访问修饰符,它只能通过接口类型访问。
Q 接口默认方法和抽象类的虚方法有什么区别?
A 接口默认方法不能访问实例字段,抽象类虚方法可以;默认方法是通过接口调用的,虚方法是通过类实例调用的。

📖 小节

📝 作业

  1. 定义抽象类 Vehicle,包含抽象方法 Start() 和具体方法 Stop(),然后创建 CarBike 子类实现
  2. 定义接口 ISerializable 含方法 string Serialize(),定义接口 IDeserializable 含方法 void Deserialize(string data),创建一个类同时实现两个接口
  3. 定义两个接口 IPlayableIRecordable,都有 void Pause() 方法,用显式接口实现区分两者
  4. 创建 Product 类实现 IComparable<Product>,按价格排序,并测试 Array.Sort
  5. 为已有接口 IPrintable 添加一个带默认实现的 void PrintHeader(string title) 方法,验证旧实现类不受影响
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏