扩展方法是静态方法语法糖,用于为现有类型“假装添加方法”,不改变类型本身;默认接口方法是CLR支持的真成员,使接口可带可重写的行为。

扩展方法和默认接口方法不是“C# extension everything”这种模糊概念,而是两个明确、独立、用途截然不同的语言特性——前者是语法糖,后者是面向对象演化的正交能力。选错一个,轻则 IntelliSense 不显示、单元测试难 Mock,重则设计腐化、语义混乱。
扩展方法:给现有类型“假装加方法”,但不改变类型本身
它本质是静态方法 + this 参数修饰符 + using 导入,编译后调用的仍是静态方法。你不能靠它改变 string 的行为,只能让它“看起来”有新方法。
- 必须定义在非嵌套、非泛型的
static class里 - 调用前必须
using对应命名空间,否则 IDE 不提示、编译不识别 - 无法被重写(
override)、无法被virtual控制、无法在子类中定制逻辑 - 不能访问目标类型的私有成员,连
internal都受限(跨程序集时) - 常见误用:给接口写一堆扩展方法,结果所有实现类都得手动
using,且无法统一拦截或替换行为
默认接口方法:让接口真正“带行为”,支持多继承式组合
C# 8.0 引入,允许接口直接提供 void Log(string) 这样的具体实现。它不是语法糖,是 CLR 级别支持的真·成员,可被重写、可被多实现冲突解决、可被 Moq 等框架 Mock。
- 必须用
public访问修饰符(不能private或protected) - 若多个接口含同名默认方法,实现类必须显式
override消除歧义 - 字段、属性、构造函数、静态成员仍不支持——它只解决“方法实现共享”,不替代抽象类
- 典型适用场景:
IAsyncDisposable新增DisposeAsync默认空实现,老实现类无需改代码即可编译通过
什么时候该用哪个?看三个硬指标
不用纠结“哪个更高级”,只问三件事:
-
是否需要多态行为? —— 要支持
mock.Setup(x => x.Log("a")).Returns(...)?→ 选默认接口方法 -
是否作用于密封类型(如
string,DateTime)? —— 它们无法实现新接口 → 只能用扩展方法 -
是否希望行为随接口契约一起传播? —— 比如所有
ILogger都应有LogWarning,且不同实现可定制?→ 默认方法;如果只是“顺手加个ToJsonString()”辅助功能?→ 扩展方法更轻量
最常被忽略的一点:默认接口方法在未显式转换为接口类型时不可见。比如 var logger = new ConsoleLogger(); logger.LogError(...) 会报错,必须写成 ((ILogger)logger).LogError(...) 或声明为 ILogger logger = new ConsoleLogger();——这点和扩展方法“自动挂载到实例上”的直觉完全相反。


















