C#:派生类重写接口默认实现的疑问——为何需重声明接口
问题分析与解答
代码定义
public interface I { void M() => Console.WriteLine("I.M"); } public class C0 : I { public void M() => Console.WriteLine("C0.M"); } public class C1 : I { } public class D1a : C1 { public void M() => Console.WriteLine("D1a.M"); } public class D1b : C1, I { public void M() => Console.WriteLine("D1b.M"); }
执行代码
I c0 = new C0(); I c1 = new C1(); I d1a = new D1a(); I d1b = new D1b(); c0.M(); c1.M(); d1a.M(); d1b.M();
输出结果
C0.M I.M I.M D1b.M
疑问与解答
疑问点
理解C0和C1的行为,但疑惑:
- 为何D1a中的方法实现未替换接口的默认实现?
- 为何给D1b添加看似冗余的接口声明就能解决该问题?
- 通常派生类无需声明基类已实现的接口,为何重写接口默认成员实现时需要重新声明?
解答
这本质是C#接口默认方法的绑定规则导致的:
- 当通过接口类型调用方法时,CLR会优先查找直接实现该接口的类型中与接口成员绑定的实现;如果该类型没有提供自定义实现,才会使用接口的默认方法。
关于D1a的行为
D1a继承自C1,而C1是直接实现I的类型——但C1没有重写M方法,所以C1对应的I.M绑定的是接口默认实现。
D1a中定义的public void M()只是一个普通的类成员方法,并没有和接口I的M建立绑定关系(因为D1a没有显式声明实现I)。所以当用I类型变量调用D1a的M时,CLR会沿用C1继承来的接口默认实现,而非D1a的普通方法。
关于D1b的行为
当D1b显式声明: C1, I时,相当于告诉编译器:该类要重新实现接口I。此时D1b中的public void M()会被编译器关联到接口I的M成员,覆盖掉从C1继承来的接口绑定逻辑。所以通过I类型变量调用时,会执行D1b的M方法。
核心结论
接口默认方法的实现绑定是和直接实现接口的类型强关联的,派生类的普通方法无法自动替换基类继承来的接口默认实现。如果要替换,必须显式声明实现该接口,让编译器将自身方法与接口成员绑定。
内容的提问来源于stack exchange,提问作者Andrew Cooper
相关产品推荐
相关产品推荐

