C# 为何允许重写方法返回 string,却报接口实现错误 CS0738?
重写基类方法时,ClassA 使用了 C# 的“协变返回类型”:满足规则时,可以将返回类型从 object 收窄为 string。ClassB 使用的是隐式接口实现,返回类型必须与接口成员匹配。因此,声明返回 string 的 DoThing() 不能实现返回 object 的接口方法,编译器会报 CS0738。
方法实际返回一个 string 对象,与方法声明返回 string 是两回事。接口方法声明返回 object 时,可以返回字符串;这里出错的是实现方法声明的返回类型。
为什么 ClassA 可以通过编译?
C# 9 引入了对类方法重写时使用协变返回类型的支持。对于示例中的普通返回值方法,只要满足相应的类型转换规则,且目标运行时支持该特性,重写方法就可以返回更具体的引用类型。
string 可以隐式转换为 object,因此下面的重写符合这一规则:
public abstract class MyBase
{
public abstract object DoThing();
}
public class ClassA : MyBase
{
public override string DoThing() => "";
}
调用表达式的静态类型取决于调用处可见的方法声明:
ClassA derived = new ClassA();
string text = derived.DoThing();
MyBase baseReference = derived;
object value = baseReference.DoThing();
两次调用都会执行 ClassA.DoThing()。通过 MyBase 引用调用时,编译器看到的返回类型是 object。
为什么接口实现不接受同样的声明?
接口实现与基类方法重写使用不同的成员匹配规则。
在题示的隐式接口实现中,编译器需要找到符合 MyInterface.DoThing() 声明的方法。方法名称和参数列表相同还不够,返回类型也必须匹配。接口声明返回 object,ClassB.DoThing() 却声明返回 string,因此不能建立实现关系。
从类型安全的角度看,返回 string 可以满足调用者接收 object 的需求,但编译器不会因此自动将该方法认定为接口实现。类方法重写的协变返回规则不直接适用于这里。
修复方式一:将实现方法声明为返回 object
如果通过 ClassB 调用时不需要直接获得 string 类型,可以按接口要求声明返回类型:
public class ClassB : MyInterface
{
public object DoThing() => "";
}
实际返回的对象仍然是字符串。返回表达式中的 string 会隐式转换为 object,与声明的返回类型相容。
通过 ClassB.DoThing() 调用时,表达式的静态类型也是 object。
修复方式二:显式实现接口,保留返回 string 的公开方法
如果希望通过 ClassB 调用时得到 string,同时通过 MyInterface 调用时满足 object 返回类型,可以增加一个显式接口实现:
public class ClassB : MyInterface
{
public string DoThing() => "";
object MyInterface.DoThing() => DoThing();
}
调用效果如下:
ClassB instance = new ClassB();
string text = instance.DoThing();
MyInterface interfaceReference = instance;
object value = interfaceReference.DoThing();
object MyInterface.DoThing() 是用于实现接口的成员。它转调公开的 string DoThing(),并将返回值作为 object 返回。显式接口实现不添加 public 修饰符,需要通过接口类型的引用调用。
这种写法也不是仅凭返回类型重载两个普通方法;显式接口实现使用了 MyInterface.DoThing 这一限定成员名称。
版本与类型方面的注意事项
如果 ClassA 也无法编译,请检查项目使用的 C# 语言版本和目标运行时是否支持协变返回类型。该写法不能直接用于所有旧项目。
示例依赖于 string 到 object 的隐式引用转换。虽然 int 可以装箱为 object,但装箱转换不满足这里的协变返回类型要求,不能据此将重写方法的返回类型从 object 改为 int。
备注:内容仅供参考。
