接口是什么
契约:声明"实现我的类必须提供这些方法",但不提供实现。
public interface IRepository<T>
{
T? GetById(int id);
IEnumerable<T> GetAll();
void Save(T item);
void Delete(int id);
}
public class UserRepo : IRepository<User>
{
public User? GetById(int id) => ...;
public IEnumerable<User> GetAll() => ...;
public void Save(User u) => ...;
public void Delete(int id) => ...;
}
类前 : 后写接口名实现。
命名约定
C# 接口名以 I 开头:IList、IEnumerable、IDisposable。来自 Java/Kotlin 的人有点逆反,但跟着约定走——所有 .NET 库都这样。
多接口实现
public class Stream : IDisposable, IAsyncDisposable
{
public void Dispose() { ... }
public ValueTask DisposeAsync() { ... }
}
类只能继承一个基类,但可以实现任意多个接口。
接口的多态
IRepository<User> repo = new UserRepo(); // 用接口类型变量
repo.Save(user);
// 替换实现
repo = new InMemoryUserRepo(); // 单元测试用 mock 实现
接口是"面向接口编程"和 DI 的核心——业务代码依赖接口、不依赖具体实现。
接口默认方法(C# 8+)
public interface ILogger
{
void Log(string msg);
// 默认实现:实现类不重写也能用
void Info(string msg) => Log($"[INFO] {msg}");
void Error(string msg) => Log($"[ERROR] {msg}");
}
public class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
// Info / Error 都不写也能用
}
向已发布的接口加方法不破坏已有实现——给库作者大福利。
显式实现
public interface IA { void Do(); }
public interface IB { void Do(); }
public class C : IA, IB
{
void IA.Do() => Console.WriteLine("IA");
void IB.Do() => Console.WriteLine("IB");
}
var c = new C();
((IA)c).Do(); // IA
((IB)c).Do(); // IB
// c.Do(); // ❌ 显式实现只能通过接口引用调用
两个接口有同名方法时用显式实现区分。也可以隐藏一个方法只通过接口暴露——public API 简洁。
接口能定义什么
C# 接口可以有:
- 方法(含 default 实现)
- 属性(含 default getter/setter)
- 索引器
- 事件
- 静态成员(C# 11+)
不能有:实例字段、构造函数。
静态抽象成员(C# 11+)
public interface IAddable<T> where T : IAddable<T>
{
static abstract T operator +(T a, T b);
static abstract T Zero { get; }
}
public struct Vec : IAddable<Vec>
{
public double X, Y;
public static Vec operator +(Vec a, Vec b) => new() { X = a.X + b.X, Y = a.Y + b.Y };
public static Vec Zero => new();
}
public T Sum<T>(IEnumerable<T> items) where T : IAddable<T>
{
var total = T.Zero;
foreach (var x in items) total += x;
return total;
}
让接口约束静态运算符——通用泛型数学的基础。.NET 7 起 INumber<T> 等内置接口让你写真正泛型的数学代码。
常见 BCL 接口
| 接口 | 用途 |
|---|---|
IEnumerable<T> |
可遍历(foreach 用) |
IList<T> / ICollection<T> |
集合操作 |
IDisposable |
using 资源释放 |
IAsyncDisposable |
await using |
IComparable<T> |
可比较(Sort 用) |
IEquatable<T> |
自定义相等 |
IDictionary<K, V> |
键值映射 |
INotifyPropertyChanged |
数据绑定通知 |
记住这些——日常 C# 离不开。
接口 vs 抽象类
| 接口 | 抽象类 | |
|---|---|---|
| 多继承 | ✅ 多实现 | ❌ 单继承 |
| 字段 | ❌(默认实现可用静态字段) | ✅ |
| 默认实现 | ✅(C# 8+) | ✅ |
| 状态 | ❌ | ✅ |
| 构造函数 | ❌ | ✅ |
经验:
- 纯契约 / 多种实现 → 接口
- 大量共享逻辑 + 部分扩展点 → 抽象类
- 不确定 → 接口先(更灵活,将来好改)
标记接口(无成员)
public interface ISerializable { } // 没方法
仅用作"打标记"。BCL 里 ICloneable 之类——现在不推荐了,用 attribute 更清晰。
SOLID 中的 I
接口隔离原则(Interface Segregation):
// ❌ 一个超大接口
public interface IWorker
{
void Eat();
void Sleep();
void Code();
void Refactor();
void DeployToProd();
}
// ✅ 小接口
public interface IEater { void Eat(); }
public interface ISleeper { void Sleep(); }
public interface ICoder { void Code(); }
实现类按需组合接口——比强迫人实现不需要的方法好。
→ 下一篇 record + readonly struct