Skip to content

C# - 接口

接口定义了一个契约(contract)。它指定了一组相关的功能(方法、属性、事件或索引器),类或结构体必须实现这些功能。可以将其视为能力的蓝图。接口声明了一个类能做什么,而不是如何做。

接口是 C# 面向对象编程的基石。它们实现了多态性,促进了依赖注入,并允许创建松散耦合、可测试和可维护的系统。

接口使用 interface 关键字声明。按照惯例,接口名称以大写字母 I 开头(例如,ILogger)。

// 1. 声明接口(契约)
public interface ILoggable
{
// 实现类必须提供的方法
void LogMessage(string message);
// 实现类必须提供的属性
string LogLevel { get; set; }
}
// 2. 在类中实现接口
public class ConsoleLogger : ILoggable
{
public string LogLevel { get; set; } = "Info";
public void LogMessage(string message)
{
Console.WriteLine($"[{LogLevel.ToUpper()}]: {message}");
}
}
// 3. 使用实现
ILoggable logger = new ConsoleLogger();
logger.LogLevel = "Warning";
logger.LogMessage("The system is running low on memory.");
/*
* 预期输出:
* [WARNING]: The system is running low on memory.
*/

自 C# 8.0 起,接口可以为其成员提供默认实现。这对于向现有接口添加新功能而不会破坏所有已实现它的类非常有用。

public interface ILoggable
{
void LogMessage(string message);
string LogLevel { get; set; }
// 一个默认实现。类不需要实现此方法。
void LogError(Exception ex)
{
// 默认行为是将异常消息记录为严重错误。
var originalLevel = LogLevel;
LogLevel = "Critical";
LogMessage($"An error occurred: {ex.Message}");
LogLevel = originalLevel;
}
}
// 此类仅实现必需的成员。
// 它将继承默认的 LogError 行为。
public class FileLogger : ILoggable
{
public string LogLevel { get; set; } = "Info";
private readonly string _filePath = "log.txt";
public void LogMessage(string message)
{
File.AppendAllText(_filePath, $"[{LogLevel.ToUpper()}]: {message}\n");
}
}
// 用法
ILoggable fileLogger = new FileLogger();
fileLogger.LogError(new InvalidOperationException("Disk full")); // 使用默认方法

接口和抽象类都用于抽象,但它们有不同的用例:

FeatureInterfaceAbstract Class
Multiple Inheritance一个类可以实现多个接口。一个类只能继承一个抽象类。
Fields and State不能包含实例字段(状态)。可以包含字段和状态。
Constructors不能有构造函数。可以有构造函数。
Primary Use Case定义能力或角色(例如,IDisposable、IEquatable)。适用于“能做什么”的关系。定义一组相关对象的基类型。适用于“是一种”的关系。

指南:优先使用接口来定义可由任何类实现的契约。当你需要为一组派生类提供通用、共享的实现细节时,请使用抽象类。

.NET 框架包含许多强大的接口。理解它们是编写地道 C# 代码的关键。

此接口允许对象被迭代,最常见的是通过 foreach 循环。使用 yield return 关键字可以极其简单地实现它。

using System.Collections;
using System.Collections.Generic;
// 此类现在可以在 foreach 循环中使用。
public class MyNumberRange : IEnumerable<int>
{
private readonly int _start, _end;
public MyNumberRange(int start, int end)
{
_start = start;
_end = end;
}
// 编译器会为你将其转换为状态机!
public IEnumerator<int> GetEnumerator()
{
for (int i = _start; i <= _end; i++)
{
yield return i;
}
}
// 兼容性所需的非泛型版本
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 用法
var numbers = new MyNumberRange(1, 5);
foreach (var num in numbers)
{
Console.Write($"{num} "); // 输出:1 2 3 4 5
}

实现此接口允许你的类的对象通过 List<T>.Sort() 等方法进行排序,或在排序集合中使用。

public class Product : IComparable<Product>
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
// 定义默认排序顺序(按价格)。
public int CompareTo(Product? other)
{
if (other == null) return 1;
return this.Price.CompareTo(other.Price);
}
public override string ToString() => $"{Name} - ${Price}";
}
// 用法
var products = new List<Product>
{
new() { Name = "Laptop", Price = 1200m },
new() { Name = "Mouse", Price = 25m },
new() { Name = "Keyboard", Price = 75m }
};
products.Sort(); // 对列表进行原地排序
foreach (var product in products)
{
Console.WriteLine(product);
}
/*
* 预期输出:
* Mouse - $25
* Keyboard - $75
* Laptop - $1200
*/

接口对于依赖注入(DI)至关重要,DI 是一种促进松散耦合的设计模式。类不再创建自己的依赖项,而是从外部源“注入”它们。这使得代码更容易测试,因为你可以在测试期间注入接口的模拟(mock)或伪造(fake)实现。

// 该服务依赖于 ILogger 接口,而不是具体的类。
public class NotificationService
{
private readonly ILoggable _logger;
// 依赖项通过构造函数注入。
public NotificationService(ILoggable logger)
{
_logger = logger;
}
public void SendNotification(string message)
{
_logger.LogMessage($"Sending notification: {message}");
// ... 发送通知的逻辑 ...
}
}
// 在生产环境中,你注入一个真正的日志记录器。
var consoleLogger = new ConsoleLogger();
var prodService = new NotificationService(consoleLogger);
// 在单元测试中,你可以注入一个伪造的日志记录器。
// (为此,通常使用 Moq 或 NSubstitute 等库)
public class FakeLogger : ILoggable
{
public List<string> Messages { get; } = new();
public string LogLevel { get; set; } = "Test";
public void LogMessage(string message) => Messages.Add(message);
}
var fakeLogger = new FakeLogger();
var testService = new NotificationService(fakeLogger);
testService.SendNotification("Test");
// Assert.AreEqual(1, fakeLogger.Messages.Count); // 测试断言示例