C# 接口
接口是 C# 实现多态和解耦的核心机制。和继承不同,接口是契约——只定义"能做什么",不关心"是什么"。一个类只能继承一个父类,但可以实现多个接口,这就给了 C# 类似多继承的灵活性。这一篇我们看接口的方方面面。
一、接口是什么?
接口(interface)定义了一组方法/属性的契约,但不含实现。类实现接口时,必须提供所有方法的具体实现:
// 接口:用 interface 声明,定义契约
public interface IShape
{
double Area(); // 默认 public abstract,不能有字段
double Perimeter();
}
// 用 : 实现接口(类似继承)
public class Circle : IShape
{
public double Radius { get; set; }
public Circle(double r) => Radius = r;
public double Area() => Math.PI * Radius * Radius;
public double Perimeter() => 2 * Math.PI * Radius;
}
public class Rectangle : IShape
{
public double W { get; set; }
public double H { get; set; }
public double Area() => W * H;
public double Perimeter() => 2 * (W + H);
}
// 用接口类型引用具体实现
IShape s1 = new Circle { Radius = 3 };
IShape s2 = new Rectangle { W = 2, H = 4 };
Console.WriteLine(s1.Area()); // 28.27...
Console.WriteLine(s2.Area()); // 8核心概念:
- 命名约定:接口名以大写
I开头,如IEnumerable、IDisposable、IComparable。 - 无字段:接口不能有字段,只能有方法/属性/事件/索引器。
- 多态:用接口类型引用具体实现,调用时执行具体类的方法。
二、多实现与接口继承
C# 类可以实现多个接口(解决单继承的限制):
// 一个类可以实现多个接口
public interface IDrawable
{
void Draw();
}
public interface IComparable
{
int CompareTo(object obj);
}
// 多接口
public class Photo : IDrawable, IComparable
{
public void Draw() { Console.WriteLine("画照片"); }
public int CompareTo(object obj) => 0;
}
// 接口也可以继承多个接口
public interface IResizable : IDrawable
{
void Resize(double scale);
}
public class AdvancedPhoto : IResizable
{
public void Draw() { }
public void Resize(double scale) { }
}设计建议:
- 多接口 > 单继承:大多数 OO 设计原则推荐组合多接口而非深层继承。
- 接口 segregation:多个小接口优于一个大而全的接口(接口隔离原则 ISP)。
- 接口可继承接口:用于组合多个契约。
三、显式实现(解决冲突)
当一个类实现的多个接口有同名方法时,用显式实现区分:
public interface ILogger
{
void Log(string msg);
}
public interface IDatabase
{
void Log(string sql); // 同名方法
}
// 显式实现:解决多接口同名冲突
public class DualLogger : ILogger, IDatabase
{
// 显式实现:用 接口名.方法名
void ILogger.Log(string msg)
{
Console.WriteLine($"[File] {msg}");
}
void IDatabase.Log(string sql)
{
Console.WriteLine($"[DB] {sql}");
}
}
var d = new DualLogger();
// d.Log("hi"); // ❌ 显式实现不能直接调用
((ILogger)d).Log("hi"); // [File] hi
((IDatabase)d).Log("SELECT"); // [DB] SELECT显式实现的特点:
- 不能直接调用:必须转成接口类型再调用。
- 隐藏实现:对外不暴露接口方法,只在用接口类型时可见。
- 解决冲突:多个接口同名时唯一选择。
- 常用于封装:不想让接口方法出现在类的公开 API 中。
常见场景:List<T> 显式实现了 IList(非泛型),让你不能意外往 List<int> 加 string。
四、默认接口方法(C# 8+)
C# 8 引入了默认接口方法(Default Interface Method, DIM),接口可以有方法体:
// C# 8+ 默认接口方法(DIM)
public interface ILogger
{
void Log(string msg);
// 默认实现:接口里可以有方法体
void Error(string msg)
{
Log($"[ERROR] {msg}");
}
void Info(string msg)
{
Log($"[INFO] {msg}");
}
}
// 实现类只需实现必需方法,其他用默认
public class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
// Error 和 Info 用接口默认实现
}
var logger = new ConsoleLogger();
logger.Error("出错啦"); // [ERROR] 出错啦
logger.Info("启动完成"); // [INFO] 启动完成DIM 的用途:
- API 演进:给老接口加新方法不破坏现有实现。
- 默认行为:复用通用逻辑,实现类可选择覆盖。
- 类似 trait/mixin:多继承的有限实现。
注意:DIM 有些复杂规则( diamond 问题),日常用得少,但理解它能读懂 .NET 源码。
五、接口 vs 抽象类
这是 C# 设计的永恒话题。两者都能定义契约,但用途不同:
- 接口:多实现、纯契约、无状态。
IComparable、IEnumerable、IDisposable。 - 抽象类:单继承、有状态(字段)、共享实现。
Stream、TextWriter、HttpMessageHandler。
选择建议:
- "能做什么"用接口(可飞、可序列化、可比较)。
- "是什么"用抽象类(动物、形状、车辆)。
- 需要共享代码用抽象类,需要共享契约用接口。
- 多继承场景:用接口。
实际项目通常接口 + 抽象类组合:接口定义契约,抽象类提供基础实现。
六、常用 .NET 接口
// IEnumerable<T> / IQueryable<T>:可遍历
// List、Array、Dictionary 都实现 IEnumerable<T>
foreach (int n in new List<int>()) { }
// IDisposable:资源释放(配合 using)
using (var fs = new FileStream("a.txt", FileMode.Open))
{
// fs 自动 Dispose
}
// IComparable<T>:排序
public class Student : IComparable<Student>
{
public int Score { get; set; }
public int CompareTo(Student other) => Score.CompareTo(other.Score);
}
// IEquatable<T>:类型安全的相等比较
public class Point : IEquatable<Point>
{
public int X, Y;
public bool Equals(Point other) => X == other.X && Y == other.Y;
}
// ICloneable:克隆(虽然不推荐用)
// IFormattable:格式化输出
// INotifyPropertyChanged:数据绑定(WPF/MAUI 核心)七、策略模式(经典应用)
// 接口 + 多实现 = 策略模式
public interface IDiscountStrategy
{
decimal Apply(decimal price);
}
public class NoDiscount : IDiscountStrategy
{
public decimal Apply(decimal price) => price;
}
public class TenPercentOff : IDiscountStrategy
{
public decimal Apply(decimal price) => price * 0.9m;
}
public class BlackFriday : IDiscountStrategy
{
public decimal Apply(decimal price) => price * 0.5m;
}
// 上下文:通过接口持有策略,可灵活替换
public class Order
{
private readonly IDiscountStrategy _discount;
public Order(IDiscountStrategy discount) => _discount = discount;
public decimal Total(decimal price) => _discount.Apply(price);
}
// 不同策略,同一接口
var o1 = new Order(new NoDiscount());
var o2 = new Order(new TenPercentOff());
var o3 = new Order(new BlackFriday());
Console.WriteLine(o1.Total(100)); // 100
Console.WriteLine(o2.Total(100)); // 90
Console.WriteLine(o3.Total(100)); // 50八、依赖注入(DI)—— 接口的现代应用
现代 .NET 项目大量用接口实现依赖反转:
// 接口:契约
public interface IUserRepository
{
User GetById(int id);
}
// 生产实现:数据库
public class DbUserRepository : IUserRepository
{
public User GetById(int id) { /* 查数据库 */ }
}
// 测试实现:内存(mock)
public class MockUserRepository : IUserRepository
{
public User GetById(int id) => new User { Id = id };
}
// 业务类:依赖接口,不依赖具体实现
public class UserService
{
private readonly IUserRepository _repo;
// 构造函数注入
public UserService(IUserRepository repo) => _repo = repo;
public User GetUser(int id) => _repo.GetById(id);
}
// 在 ASP.NET Core 容器里注册
// services.AddScoped<IUserRepository, DbUserRepository>();这是现代 .NET 开发的标准模式:接口 + DI 容器,实现:
- 解耦:业务代码不依赖具体实现。
- 易测试:单元测试可注入 Mock。
- 易替换:换数据库只需改注册。
- 易扩展:加新实现不改老代码(开闭原则)。
九、集合 + 接口 + LINQ 联动
// 接口与"鸭子类型":C# 编译期静态匹配
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly() => Console.WriteLine("鸟飞");
}
public class Airplane : IFlyable
{
public void Fly() => Console.WriteLine("飞机飞");
}
// 集合 + 多态
List<IFlyable> flyers = new()
{
new Bird(),
new Airplane()
};
foreach (var f in flyers)
f.Fly();
// 鸟飞 / 飞机飞
// 参数:用接口而非具体类型
void TakeOff(IFlyable f) => f.Fly();接口 + 集合 + 多态,是 C# 表达力最强的组合。LINQ 之所以能用一套方法对 List、Array、HashSet 都生效,就是因为它们都实现了 IEnumerable<T>。
十、接口设计原则
- 接口隔离原则(ISP):不要塞太多方法,小而专。
- 依赖反转原则(DIP):高层模块依赖接口,不依赖具体实现。
- 开闭原则(OCP):对扩展开放,对修改关闭(加新接口不改老的)。
- 命名约定:接口加
I前缀(虽然部分新派别建议去掉,但 .NET 主流仍保留)。 - 避免胖接口:一个接口超过 5 个方法考虑拆分。
小结
接口是 C# 实现多态、解耦、测试、扩展的核心工具。记住几个要点:多实现(扩展能力)、显式实现(解决冲突)、接口隔离(小而专)、依赖注入(现代标配)。下一篇我们看 C# 的集合——大量使用接口(IEnumerable、ICollection、IList)。
← 上一篇 C# 继承与多态
下一篇 C# 集合 →