C# 9.0新特性records - ToString未被继承

10
考虑:
// the ratioale for Wrapper is that it has a Json serializer that  
// serialize through Field (not included in this example)
record Wrapper<T> where T : notnull {
  protected Wrapper(T field) => Field = field; 
  protected Wrapper(Wrapper<T> wrapper) => Field = wrapper.Field;
  protected readonly T Field;
  public override string ToString() => Field.ToString() ?? "";
}

record MyRec : Wrapper<string> {
    public MyRec(string s) : base(s) {}
}

public static class Program {
  public static void Main(string[] args) {
      var r = new MyRec("hello");
      Console.WriteLine(r.ToString());
  }
}

{{链接1:sharplab}}

看起来基类 ToString 没有被继承,编译器仍然会自动生成派生的 Tostring

为什么会这样?有没有好的解决方法?


1
你想让ToString返回表示包装对象的字符串,对吧?返回序列化的包装器通常不会有用。 - Maxx
是的,我提到序列化是为了说明Wrapper的理由。 - kofifus
你的意思是希望在你的Wrapper类中覆盖一个方法,并且当你从该Wrapper派生另一个类时,该覆盖方法被调用,但你现在得到了Object.ToString()的调用,是这样吗? - Maxx
2
只需覆盖它自己,返回base.ToString()。 - Hans Passant
3
C# 10将增加密封ToString方法的功能,这样你就可以得到你想要的东西。 - Rubenisme
2个回答

21

有人留言为我解决了这个问题。Sean说,如果你在方法中添加sealed关键字,它会阻止编译器合成ToString方法,如下所示:

public sealed override string ToString() => Value;

请注意,覆盖 ToString 的密封能力是在 C# 10中引入的。


这个回答被低估了很多! - Christian Klemm
1
在C# 9中是不可能的:https://anthonygiretti.com/2021/07/19/introducing-c-10-seal-overriden-tostring-method-on-records/ 文章下面的一条评论说:“[...] 这个功能的重点并不是防止用户覆盖该方法,而是防止编译器这样做。在C# 9中禁止将ToString方法标记为sealed,因为编译器总是会在派生记录中生成一个重写。这使得无法为整个记录层次结构实现ToString。通过这个改变,如果该方法被标记为sealed,编译器将不再生成重写。” - Tobias Knauss

12
record声明取代了继承的ToString()。这在What's New中有解释: 编译器合成了两个支持打印输出的方法:一个是ToString()覆盖,另一个是PrintMembers。 事实上,基类(同样是record)具有ToString()覆盖并不重要。从技术上讲,所有类型都从object继承了ToString(),因此record类型的代码生成器不会查看继承的基类型是否已重载ToString()。否则,就不会创建由编译器生成的ToString()。 (您可以认为应该保留继承的record类型的ToString(),但这不是该功能设计的方式)。 但是,如果记录类型具有重写的ToString(),则编译器不会生成该方法: 如果记录类型具有与任何合成方法的签名匹配的方法,则编译器不会合成该方法。 您可以定义一个看似多余的覆盖:
public override string ToString() => base.ToString();

通过这样做,编译器不会因为record的声明而自动生成ToString重写。


@kofifus 是的 - 如果您定义一个类,它将会继承自objectToString方法(或者我是否误解了您的陈述?) - D Stanley
我所说的“编译器”,是指用于record类型的代码生成器。我在我的回答中进行了澄清。 - D Stanley
1
抱歉,我不理解你的回答。"从技术上讲,所有类型都从对象继承ToString(),因此记录类型的代码生成器不会查看基类型以获取继承的重写" - 这里的A->B逻辑是什么?是的,所有类型都从对象继承,但这并不意味着记录的代码生成器不应该像GetHashCode一样查看基类型。是否有文档指定ToString不是继承的?这让我感到困惑。 - kofifus
1
我的观点是,代码生成器不会查看父Wrapper类并确定由于在那里重写了ToString方法,它就不应该自动生成一个。由于这个类被明确定义为record,编译器会自动生成所有在该类中未明确定义的record方法。如果你想要“继承”自定义方法,在这种情况下你必须显式地这样做。 - D Stanley
5
你可能需要更新你的回答。在 C# 10 中,现在可以使用 sealed 关键字来封印 .ToString() 方法的重写,从而让编译器不再为继承记录自动生成 .ToString() 方法。 - Sean Amos
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接