CodeGym /课程 /C# SELF /接口和抽象类的对比

接口和抽象类的对比

C# SELF
第 24 级 , 课程 2
可用

1. 经典区别简述

如果有人说:“接口就是一堆签名”,你就问:“你写的是哪个版本的C#?”从C# 8开始,接口已经强大多了。现在是时候把它们和抽象类好好对比下——不光是老的那些属性,还要看.NET平台的新花样。

如果回到几年前——C# 7那会儿——一切都很简单。抽象类可以有字段和部分实现的方法,接口只能有签名(方法、属性、事件、索引器)。
抽象类的继承是"是"关系(Is-a),接口实现的是多重行为继承("能做",can-do)。

特性 抽象类 接口(C# 8之前)
关系 is-a can-do
继承 只能一个 可以多个
字段 可以有 不能有
方法实现 可以 不行
构造函数 可以 不行
访问修饰符 各种(public, protected, ...) 只能隐式public

你看,以前抽象类就是接口的“老大哥”——更强更灵活。但一切都在变!

2. 带默认实现的接口

自从C# 8(更别说C# 14和.NET 9)出来后,接口有了新超能力——默认实现方法,英文叫"Default Interface Methods"(DIM)。

长啥样?


public interface IAnimal
{
    void SayHello();

    // 带默认实现的方法!
    void Walk()
    {
        Console.WriteLine("我在走路...");
    }
}

哇哦! 现在接口可以有方法实现了。而且不限一个,想加几个都行。但有个点:这些方法必须有方法体,其他的(字段、私有方法、构造函数)还是不行。

3. 现代接口的新能力

现在.NET开发者必须知道的新花样:

  • 带默认实现的方法。
  • 接口里的私有方法(只能给接口里的其他方法用,外面用不了)。
  • 静态方法(C# 8起)。
  • 带默认实现的属性。
  • 静态字段(C# 14起——"static interface members")。
  • 抽象静态成员("abstract static members"——现在接口可以要求实现类必须有某些静态方法!)。

现代接口完整例子:


public interface ILogger
{
    static int LoggerCount { get; set; } // C# 14

    void Log(string message); // 签名(契约)

    // 默认实现
    void LogWarning(string warning)
    {
        Log("[WARNING]: " + warning);
    }
    
    // 接口里的私有辅助方法(C# 8+)
    private void FormatAndLog(string level, string msg)
    {
        Log($"{level}: {msg}");
    }

    // 接口里的静态方法(C# 8+)
    static void PrintLoggerInfo()
    {
        Console.WriteLine("接口ILogger——你的最佳助手!");
    }
}

想象下——以前这根本不可能,就像让猫去当服务器保安一样离谱。

4. 抽象类:这些年有啥新变化?

抽象类嘛……说实话,这十年没啥大变化。它们还是可以有:

  • 字段(包括私有、受保护、静态的)。
  • 实现的方法和抽象方法。
  • 构造函数(对,可以写初始化逻辑)。
  • 属性、事件、索引器。
  • 静态和实例成员。

抽象类例子:


public abstract class Animal
{
    public string Name { get; set; }

    public abstract void Speak();

    public virtual void Walk()
    {
        Console.WriteLine($"{Name} 用爪子走路!");
    }

    protected void Eat()
    {
        Console.WriteLine($"{Name} 在吃猫粮。");
    }
}

抽象类依然是存放通用逻辑、状态和行为的好地方,适合一整个类族用。

5. 现代对比:新能力下的表格

特性 抽象类 接口(C# 14+,.NET 9)
关系 is-a(是) can-do(能做)
继承 只能一个 可以多个
字段 可以,随便 只能静态*(C# 14+)
构造函数 没有
方法实现 有(virtual/abstract 有(default, static, abstract static)
带实现的属性 有(default implementation)
私有成员 有(只限方法,C# 8+)
静态成员 有(C# 8+,有限制)
静态字段 有*(C# 14+)
访问修饰符 随便 默认public或private

*——接口里的静态字段一般只在特殊场景用,这功能很新鲜。

6. 什么时候用接口,什么时候用抽象类:现代建议

接口(现在还能有默认实现)——用来做契约,让组件之间协作。最大特点:多用。你的类可以实现十个接口,妥妥的多面手。

抽象类适合这些场景:

  • 需要通用状态(字段)、逻辑和行为,其他类继承。
  • 需要标准但可重写的逻辑(用virtual)。
  • 想统一初始化(用构造函数)。

实际项目里常见套路:纯契约用接口,要是有通用代码或基础设施就搞个抽象基类。


.                     ┌────────────────────────┐
                      │      接口              │
                      │  (契约:能做啥)      │
                      └─────────┬──────────────┘
                                │
                 ┌──────────────┼──────────────┐
                 │              │              │
           实现1         实现2 ...        实现N
             MyLogger     CloudLogger     FileLogger
    
        (可以和抽象类继承一起用)
图示:啥场景用啥

7. 场景对比——谁更适合

多重实现:
比如你有个接口IDrivable和抽象类Vehicle。现在Car类可以继承Vehicle,同时实现好几个接口(IDrivableIRepairableIInsurable)。要是你有个抽象类Repairable,那只能选Vehicle还是Repairable!接口这时候就赢了。

通用逻辑和状态:
比如所有“交通工具”都有个“车牌号”字段。这就得放抽象类里。接口里没法放字段(除了静态的)。

API进化:
Default Interface Methods的革命性——现在接口能进化,不怕把老代码搞崩。
比如你给接口加了个带默认实现的新方法——一切正常,老实现都能用!以前这事儿要么很痛苦,要么根本做不到。

8. 实战例子

我们这个学习项目慢慢加上了日志功能。来写个带默认实现的ILogger接口:


public interface ILogger
{
    void Log(string message);

    // 默认实现所有实现类都能用!
    void LogInfo(string info)
    {
        Log("[INFO] " + info);
    }

    // 接口静态方法
    static void PrintHelp()
    {
        Console.WriteLine("用ILogger来记录事件");
    }
}

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

// 某处代码:
ILogger logger = new ConsoleLogger();
logger.LogInfo("系统已启动!"); // 多亏了default实现

// 调用接口静态方法
ILogger.PrintHelp();

如果我们给接口加了个带默认实现的新方法,所有现有实现(比如ConsoleLogger)自动就有了——不用慌,代码不会崩。

9. 错误和细节:实战坑点

要知道,事情没想象的那么美好。比如接口有default实现,但你用类类型访问对象,不用接口类型,那default实现只能通过接口用。


ConsoleLogger log = new ConsoleLogger();
log.LogInfo("Hello"); // 编译不过:LogInfo类里没定义!

ILogger log2 = log;
log2.LogInfo("Hello"); // 没问题!

这有点像接口的显式实现。有时候这样能隐藏“多余”的API,有时候新手会觉得很奇怪。

2
任务
C# SELF, 第 24 级, 课程 2
已锁定
使用带有默认实现方法的接口
使用带有默认实现方法的接口
2
任务
C# SELF, 第 24 级, 课程 2
已锁定
在接口中使用静态方法
在接口中使用静态方法
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION