接口成员默认 public,显式添加会编译报错 CS0106;所有成员隐式为 public abstract(方法)或 public(属性/事件),不可加任何访问修饰符。

接口定义时为什么不能用 public 修饰符?
因为 C# 接口成员默认就是 public,显式加 public 会直接编译报错:CS0106: The modifier 'public' is not valid for this item。接口本身可以是 public 或 internal(取决于所在程序集可见性),但里面的方法、属性、事件等一律不允许加访问修饰符。
常见错误是照搬类的写法,比如:
public interface ILogger
{
public void Log(string message); // ❌ 编译失败
}正确写法是直接省略:
public interface ILogger
{
void Log(string message); // ✅
string Name { get; }
}- 接口中所有成员隐式为
public abstract(方法)或public(属性/事件),不可更改 - 即使写
private或protected,也会被拒绝——接口不支持实现细节封装 - .NET 8 起支持默认接口方法(
default interface methods),此时可加public,但仅限于有具体实现的成员,且仍不能加private
什么时候该用接口而不是抽象类?
核心判断依据是「是否需要共享实现」和「是否要约束多个不相关类型」。
接口适合定义契约:多个完全无关的类(比如 FileLogger、ConsoleLogger、DatabaseLogger)都要提供 Log 行为,但实现逻辑毫无共性。这时用接口最干净。
- 需要多继承能力 → 必须用接口(C# 不支持多类继承)
- 想让结构体(
struct)也实现某行为 → 只能靠接口(结构体不能继承类) - 未来可能由第三方实现 → 接口更轻量,无基类耦合风险
- 要带状态或构造逻辑 → 优先考虑抽象类(接口不能有字段、构造函数、
protected成员)
一个典型反例:如果多个日志器共用格式化字符串、时间戳生成逻辑,又不想重复写,那更适合抽一个 BaseLogger 抽象类,再让接口只声明顶层契约(如 ILogger),二者配合用。
as 和 is 在接口转换时有什么坑?
用 as 做接口转换比强制转型((IAsyncDisposable)x)安全,但容易忽略返回 null 的可能性;而 is 判断后直接强转,又可能因竞态导致 NullReferenceException。
错误写法:
if (obj is IAsyncDisposable)
{
((IAsyncDisposable)obj).DisposeAsync(); // ❌ obj 可能在判断后被改写或释放
}推荐写法(.NET 7+):
if (obj is IAsyncDisposable disposable)
{
await disposable.DisposeAsync(); // ✅ 模式匹配自动赋值,无竞态
}-
as转换后务必判空,尤其在泛型方法里容易漏:var logger = obj as ILogger; if (logger != null) logger.Log(...) - 对值类型做
as接口转换会静默失败(返回null),但值类型本身不可能为null→ 实际上会触发装箱,性能有损耗 - 高频路径(如循环内)避免反复
is+ 强转,优先用模式匹配一次完成判断和变量捕获
接口版本演进时如何避免破坏现有实现?
给已有接口加新方法等于强制所有实现类补实现,属于二进制不兼容变更。.NET 5+ 提供了两个缓解手段:默认接口方法(DIM)和标记新接口为 [Obsolete] 并引入新接口。
例如,给 IRepository<t></t> 新增异步查询能力:
- ❌ 直接加
Task<t> FindAsync(int id);</t>→ 所有实现编译失败 - ✅ 方案一(推荐):用 DIM 提供空实现或抛
NotSupportedException
public interface IRepository<T>
{
T Find(int id);
// 新增,默认实现抛异常,留给子类选择性覆盖
Task<T> FindAsync(int id) => throw new NotSupportedException();
}- ✅ 方案二:定义
IAsyncRepository<t> : IRepository<t></t></t>,让新消费者明确依赖新接口 - DIM 方法不能是
virtual或sealed,也不能访问私有字段(毕竟没实例) - 注意:.NET Framework 不支持 DIM,跨框架项目慎用
真正难处理的是已上线、被第三方广泛实现的公共接口——这时候加方法几乎必然破环,只能靠新接口 + 工厂适配器兜底。


















