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

核心概念:

二、多实现与接口继承

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) { }
}

设计建议:

三、显式实现(解决冲突)

当一个类实现的多个接口有同名方法时,用显式实现区分:

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

显式实现的特点:

常见场景: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 的用途:

注意:DIM 有些复杂规则( diamond 问题),日常用得少,但理解它能读懂 .NET 源码。

五、接口 vs 抽象类

这是 C# 设计的永恒话题。两者都能定义契约,但用途不同:

选择建议:

实际项目通常接口 + 抽象类组合:接口定义契约,抽象类提供基础实现。

六、常用 .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 容器,实现:

九、集合 + 接口 + 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>

十、接口设计原则

小结

接口是 C# 实现多态、解耦、测试、扩展的核心工具。记住几个要点:多实现(扩展能力)、显式实现(解决冲突)、接口隔离(小而专)、依赖注入(现代标配)。下一篇我们看 C# 的集合——大量使用接口(IEnumerable、ICollection、IList)。

← 上一篇 C# 继承与多态

下一篇 C# 集合

✈️💬