Skip to content

19-接口(Interface)

接口定义了一组方法签名,但不提供实现。实现接口的类必须实现接口中定义的所有成员。接口描述了"能做什么"(能力),而不是"是什么"(类型)。


一、接口 vs 抽象类 vs 类

对比项类(Class)抽象类(Abstract Class)接口(Interface)
实例化不能不能
方法实现全部有实现部分有实现全部无实现(C# 8.0+ 可有默认实现)
字段可以有可以有不能有
构造函数可以有可以有不能有
多继承不支持不支持支持(一个类可实现多个接口)
使用场景"是什么""是什么" + "部分默认行为""能做什么"(能力合约)

二、接口的定义和实现

定义接口

csharp
// 命名规范:以大写 I 开头
public interface IFlyable
{
    void Fly();   // 方法签名,没有方法体
    void Land();
}

public interface ISwimmable
{
    void Swim();
}

实现接口

实现接口的类必须实现所有接口成员

csharp
// 实现单个接口
public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("鸟儿在飞翔");
    }

    public void Land()
    {
        Console.WriteLine("鸟儿着陆");
    }
}

// 实现多个接口(使用逗号分隔)
public class Duck : IFlyable, ISwimmable
{
    public void Fly()
    {
        Console.WriteLine("鸭子飞起来了");
    }

    public void Land()
    {
        Console.WriteLine("鸭子着陆");
    }

    public void Swim()
    {
        Console.WriteLine("鸭子在游泳");
    }
}

// 使用
IFlyable bird = new Bird();
bird.Fly();
bird.Land();

Duck duck = new Duck();
duck.Fly();     // 类自身方法
duck.Swim();    // 类自身方法

// 通过接口引用调用
IFlyable flyable = duck;
flyable.Fly();  // 只能调用 IFlyable 的方法

ISwimmable swimmable = duck;
swimmable.Swim();  // 只能调用 ISwimmable 的方法

三、接口作为参数和返回值

接口最大的威力在于可以编写面向接口的代码,与具体实现解耦。

csharp
// 定义日志接口
public interface ILogger
{
    void Log(string message);
}

// 实现1:控制台日志
public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[Console] {DateTime.Now}: {message}");
    }
}

// 实现2:文件日志
public class FileLogger : ILogger
{
    private string path;

    public FileLogger(string path)
    {
        this.path = path;
    }

    public void Log(string message)
    {
        string line = $"[File] {DateTime.Now}: {message}";
        File.AppendAllText(path, line + Environment.NewLine);
    }
}

// 实现3:空日志(不执行任何操作)
public class NullLogger : ILogger
{
    public void Log(string message) { }
}

// 函数接受接口参数——与具体实现解耦
public class OrderService
{
    private ILogger logger;

    // 依赖注入:通过构造函数传入具体实现
    public OrderService(ILogger logger)
    {
        this.logger = logger;
    }

    public void CreateOrder(string orderId)
    {
        // 业务逻辑...
        logger.Log($"订单 {orderId} 已创建");
    }
}

// 使用
var service1 = new OrderService(new ConsoleLogger());
service1.CreateOrder("ORD-001");  // 控制台输出

var service2 = new OrderService(new FileLogger(@"C:\log.txt"));
service2.CreateOrder("ORD-002");  // 输出到文件

var service3 = new OrderService(new NullLogger());
service3.CreateOrder("ORD-003");  // 无输出

四、显式接口实现

当类实现多个接口且方法签名冲突时,使用显式接口实现来区分。

csharp
public interface IWriter
{
    void Write();
}

public interface IReader
{
    void Write();  // 同名方法
}

public class Device : IWriter, IReader
{
    // 显式接口实现:方法名前加接口名
    void IWriter.Write()
    {
        Console.WriteLine("IWriter:写入数据");
    }

    void IReader.Write()
    {
        Console.WriteLine("IReader:读取数据");
    }

    // 公有方法
    public void Write()
    {
        Console.WriteLine("Device:默认操作");
    }
}

// 使用
Device device = new Device();
device.Write();  // 调用公有方法:Device:默认操作

IWriter writer = device;
writer.Write();  // 调用显式实现:IWriter:写入数据

IReader reader = device;
reader.Write();  // 调用显式实现:IReader:读取数据

显式接口实现的特点

  • 只能通过接口引用调用,不能通过类实例调用
  • 方法默认是 private 的(不需要也不能写访问修饰符)
  • 常用于解决多接口方法冲突
  • 也用于隐藏"不常用"的接口成员

五、接口继承

接口之间可以相互继承。

csharp
public interface IWorkable
{
    void Work();
}

public interface IManageable : IWorkable  // 接口继承
{
    void Manage();
}

// 实现类必须实现所有接口的成员
public class Manager : IManageable
{
    public void Work()        // 来自 IWorkable
    {
        Console.WriteLine("经理在工作");
    }

    public void Manage()      // 来自 IManageable
    {
        Console.WriteLine("经理在管理");
    }
}

// 或者同时实现多个接口(效果类似)
public class Worker : IWorkable, IDisposable
{
    public void Work() { }
    public void Dispose() { }
}

六、接口中的属性

csharp
public interface IAnimal
{
    string Name { get; set; }   // 可读写属性
    int Age { get; }            // 只读属性
    string Species { get; }     // 只读属性
    void Speak();
}

public class Dog : IAnimal
{
    public string Name { get; set; }
    public int Age { get; }
    public string Species => "犬科";

    public Dog(int age)
    {
        Age = age;
    }

    public void Speak()
    {
        Console.WriteLine($"{Name} 在汪汪叫");
    }
}

七、内置接口(常用)

接口作用典型使用
IComparable<T>定义比较方法用于排序list.Sort() 要求元素实现
IEquatable<T>定义相等比较Dictionary 键比较
IDisposable释放非托管资源using 语句
IEnumerable<T>支持 foreach 遍历所有集合类
ICloneable支持克隆obj.Clone()
IFormattable支持格式化字符串ToString("N2")

IDisposable 示例

csharp
public class FileManager : IDisposable
{
    private FileStream? stream;

    public void Open(string path)
    {
        stream = File.OpenRead(path);
    }

    public void Dispose()
    {
        stream?.Close();
        Console.WriteLine("文件资源已释放");
    }
}

// 使用 using 确保自动释放
using (var fm = new FileManager())
{
    fm.Open(@"C:\test.txt");
    // 使用文件...
}  // 自动调用 Dispose()

IComparable 和 IEnumerable 示例

csharp
// IComparable:让对象可排序
public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        if (other == null) return 1;
        // 按分数排序(升序)
        return Score.CompareTo(other.Score);
    }

    public override string ToString() => $"{Name}: {Score}";
}

// IEnumerable:让类支持 foreach
public class Classroom : IEnumerable<Student>
{
    private List<Student> students = new List<Student>();

    public void Add(Student student) => students.Add(student);

    public IEnumerator<Student> GetEnumerator()
    {
        return students.GetEnumerator();
    }

    // 非泛型版本的实现(必须)
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// 使用
var classroom = new Classroom();
classroom.Add(new Student { Name = "张三", Score = 85 });
classroom.Add(new Student { Name = "李四", Score = 92 });
classroom.Add(new Student { Name = "王五", Score = 78 });

// foreach 支持(因为实现了 IEnumerable)
foreach (var student in classroom)
    Console.WriteLine(student);

// 排序支持(因为实现了 IComparable)
var list = classroom.ToList();
list.Sort();
Console.WriteLine("\n按分数排序后:");
foreach (var s in list)
    Console.WriteLine(s);

八、接口默认实现(C# 8.0+)

C# 8.0 开始,接口中可以提供方法的默认实现。

csharp
public interface ISpeak
{
    void Speak();  // 抽象方法,实现类必须实现

    // 默认实现方法(C# 8.0+)
    public void Greet()
    {
        Console.WriteLine("你好!");
    }
}

public class Person : ISpeak
{
    public void Speak()
    {
        Console.WriteLine("我是一名程序员");
    }
    // Greet 方法不需要实现,使用接口的默认实现
}

var person = new Person();
person.Speak();          // 我是一名程序员
((ISpeak)person).Greet(); // 你好!(需要通过接口调用)

九、接口与设计原则

接口隔离原则(ISP)

接口应该小而专,不应该包含实现类不需要的方法。

csharp
// ❌ 不推荐:大而全的接口
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
    void Manage();  // 不是所有工人都需要管理
}

// ✅ 推荐:拆分为多个专用接口
public interface IWorkable { void Work(); }
public interface IEatable { void Eat(); }
public interface ISleepable { void Sleep(); }
public interface IManageable { void Manage(); }

// 普通工人只需要实现需要的接口
public class Worker : IWorkable, IEatable, ISleepable
{
    public void Work() => Console.WriteLine("工作");
    public void Eat() => Console.WriteLine("吃饭");
    public void Sleep() => Console.WriteLine("睡觉");
}

// 经理额外实现管理接口
public class Manager : IWorkable, IEatable, ISleepable, IManageable
{
    public void Work() => Console.WriteLine("工作");
    public void Eat() => Console.WriteLine("吃饭");
    public void Sleep() => Console.WriteLine("睡觉");
    public void Manage() => Console.WriteLine("管理");
}

核心知识点总结

接口特性速查

特性说明
关键字interface
命名规范I 开头(如 IComparable
成员方法、属性、事件、索引器(C# 8.0+ 可包含默认实现)
字段不能包含字段
构造函数不能有构造函数
访问修饰符成员默认 public,不能显式指定(C# 8.0+ 可指定)
多实现一个类可实现多个接口
继承接口之间可以互相继承

接口 vs 抽象类选择指南

需求选择
定义"是什么"(共同基础)抽象类
定义"能做什么"(能力合约)接口
需要共享代码实现抽象类
需要多重继承接口
预计未来会添加新成员抽象类(接口加成员会破坏现有实现)

注意事项

  1. 面向接口编程——依赖接口而非具体实现,提高可扩展性
  2. 接口一旦发布,应避免修改——添加新成员会破坏所有现有实现(可使用默认实现缓解)
  3. 接口越小越好——遵循接口隔离原则(ISP)
  4. 显式接口实现用于解决多接口方法冲突
  5. 常用内置接口IDisposableIComparableIEnumerableIEquatable

Released under the MIT License.