第1章 接口(interface)是什么
1.1 定义
• 接口是一组"能力"或"契约"的抽象描述,只规定"能做什么",不规定"怎么做"。
• 在 C# 中,接口是一种完全抽象的类型(fully abstract type)。
• 关键词:interface。
• 命名规范:以大写字母 I 开头,如 ILogger、IDisposable。
1.2 语法骨架
cs
public interface 接口名
{
// 1. 属性
数据类型 属性名 { get; set; } // 自动属性写法
// 2. 方法
返回类型 方法名(参数列表);
// 3. 索引器
数据类型 this[int index] { get; set; }
// 4. 事件
event EventHandler 事件名;
}
• 成员默认 public,不允许显式写 public/private/protected。
• 不允许有字段(field) 和 实例构造函数。
第2章 实现接口的三种姿势
2.1 隐式实现(Implicit Implementation)
cs
public class FileLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"[File] {message}");
}
• 类中 public 成员签名与接口成员一致即可。
• 通过类变量或接口变量都能调用。
2.2 显式实现(Explicit Implementation)
cs
public class DbLogger : ILogger
{
void ILogger.Log(string message) // 无访问修饰符
=> Console.WriteLine($"[DB] {message}");
}
• 只能"接口变量"调用:ILogger log = new DbLogger(); log.Log(...)
。
• 解决"菱形继承"时成员命名冲突。
2.3 结构体实现接口
cs
public struct Point : IEquatable<Point>
{
public int X, Y;
public bool Equals(Point other) => X == other.X && Y == other.Y;
}
• 值类型也可实现接口,但注意装箱问题。
第3章 接口的进阶特性
cs
interface IRunnable { void Run(); }
interface ILogger { void Log(string msg); }
class Job : IRunnable, ILogger
{
public void Run() => Log("Job is running");
public void Log(string msg) => Console.WriteLine(msg);
}
3.2 接口继承接口
cs
interface IAdvancedLogger : ILogger
{
void LogError(Exception ex);
}
实现类必须实现全部成员(包括继承链)。
3.3 默认实现(C# 8.0+)
cs
public interface ILogger
{
void Log(string message);
void LogError(Exception ex) => Log($"Error: {ex.Message}");
}
• 实现类不强制实现 LogError。
• 解决"接口演化"问题,向后兼容。
3.4 静态抽象成员(C# 11+)
cs
public interface IFactory<T> where T : new()
{
static abstract T Create();
}
用于泛型数学、泛型工厂等高级场景。
第4章 完整案例:插件式日志系统
4.1 需求
• 系统同时支持控制台、文件、数据库三种日志输出。
• 可随时插拔新的日志方式。
• 调用端面向接口编程,不依赖具体实现。
4.2 设计
-
定义接口 ILogger
-
三种实现:ConsoleLogger、FileLogger、SqlLogger
-
使用"工厂 + 反射"做插件加载
-
客户端仅依赖 ILogger
4.3 代码实现
(1) 接口:
cs
public interface ILogger
{
void Log(string message);
void LogError(Exception ex);
}
(2) 控制台实现:
cs
public class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"[Console] {DateTime.Now}: {message}");
public void LogError(Exception ex) => Log($"ERROR: {ex}");
}
(3) 文件实现:
cs
public class FileLogger : ILogger
{
private readonly string _path;
public FileLogger(string path) => _path = path;
public void Log(string message)
=> File.AppendAllText(_path, $"{DateTime.Now}: {message}\n");
public void LogError(Exception ex) => Log($"ERROR: {ex}");
}
(4) 工厂(简化版):
cs
public static class LoggerFactory
{
public static ILogger Create(string type, params object[] args)
{
var t = Type.GetType(type, true);
return (ILogger)Activator.CreateInstance(t, args);
}
}
(5) 配置文件 appsettings.json
cs
{
"Logger": {
"Type": "FileLogger",
"Args": [ "app.log" ]
}
}
(6) LoggerConfig
的定义
cs
// 通常放在 Models 文件夹或 Config 文件夹下
public class LoggerConfig
{
// 对应 JSON 里的 "Type"
public string Type { get; set; }
// 对应 JSON 里的 "Args",由于 JSON 里是数组,所以用 object[]
public object[] Args { get; set; } = Array.Empty<object>();
}
(7) 客户端:
cs
var config = JsonSerializer.Deserialize<LoggerConfig>(File.ReadAllText("appsettings.json"));
ILogger logger = LoggerFactory.Create(config.Type, config.Args);
logger.Log("系统启动");
try { int x = 0, y = 1 / x; }
catch (Exception ex) { logger.LogError(ex); }
• 更换日志方式只需改配置,无需改业务代码。
第5章 常见误区与最佳实践
-
接口 vs 抽象类
• 接口:多继承、纯契约、无状态。
• 抽象类:单继承、可包含状态、可含实现。
-
接口隔离原则(ISP)
• 不要强迫客户依赖它们不用的方法。
• 把"胖接口"拆分为多个小接口。
-
命名规范
• 接口以 I 开头,实现类以接口名去掉 I 为后缀,如 SqlLogger : ILogger。
-
显式实现的代价
• 调用方必须转换成接口类型,降低可读性;除非解决冲突,否则优先隐式实现。