C# 记录类型:如何本地化自动生成的 ToString 方法

C# record types: how to localize automatically generated ToString method

我目前正在尝试熟悉 C#9 中的 record 类型。 它们是一种用于处理不可变数据的方便的新类型,并带有一些编译器生成的方法,用于诸如相等比较和字符串表示之类的事情。

为了玩玩,我创建了一个简单的 class 库和一些单元测试。 在 PC“A”上一切正常,即 运行ning Win10 英文版本。 但是当我尝试 运行 我在安装了德国 Win10 的 PC“B”上进行测试时,一些测试会失败。 原因是,自动生成的 ToString 方法将使用 PC 的默认语言环境。

现在假设您有一个非常简单的记录:

public record SimpleRecordType(double Value);

还有一个测试:

[Fact]
public void CreateStringRepresentation()
{
    var recordAsString = new SimpleRecordType(1.23).ToString();
    Assert.Equal("SimpleRecordType { Value = 1.23 }", recordAsString);
}

此测试在德国 PC 上将失败,因为字符串将如下所示: SimpleRecordType { Value = 1,23 } (逗号而不是小数点)

我知道这个测试毫无意义:我应该相信编译器会正确生成 ToString 方法。但是为了学习,也许为了日志记录或其他场景,我想对本地化有更多的控制。

现在回答我的问题: 记录应该与值一起使用,就像我的 double 一样。 那么有什么简单的方法可以将使用的 CultureInfo 更改为 CultureInvariant 同时保持自动生成的 ToString 方法的好处?

据我所知,编译器不会创建带参数的重载...

首先,编译器没有本地化这个 - 编译器只是对所有成员调用 ToString(),并且由于 double.ToString() 是自动本地化的,所以你看到的就是结果。

记录设计有意不提供任何切换来改变事物的生成方式 - 语言设计团队认为这将是一个无底洞。相反,他们将 SourceGenerators 作为最新 C# 版本的一部分发布。这些可以直接用于在记录上生成 ToString 方法或生成 ToString(IFormatProvider) 重载。

您必须将所有记录标记为 partial。然后 SourceGenerator 将检测所有记录并生成一个包含部分记录的文件。

该部分实现应定义以下方法:

partial record MyRecord
{
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        return PrintMembers(builder, null);
    }

    protected virtual bool PrintMembers(StringBuilder builder, IFormatProvider? formatProvider)
    {
        //code goes here
        return true; //return true if any members to print, false otherwise
    }
    
    public override string ToString()
    {
        return ToString(null);
    }
    
    public virtual string ToString(IFormatProvider? formatProvider)
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("MyRecord");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder, formatProvider))
        {
            stringBuilder.Append(" ");
        }
        stringBuilder.Append("}");
        return stringBuilder.ToString();
    }
}

对于继承自其他记录的记录,您将生成:

partial record SubRecord: MyRecord
{
    protected override bool PrintMembers(StringBuilder builder)
    {
        return PrintMembers(builder, null);
    }

    protected override bool PrintMembers(StringBuilder builder, IFormatProvider? formatProvider)
    {
        if (base.PrintMembers(builder, formatProvider))
        {
            builder.Append(", ");
        }
        //code goes here
        return true; //return true if any members to print, otherwise just directly return base.PrintMembers
    }
    
    public override string ToString()
    {
        return ToString(null);
    }
    
    public override string ToString(IFormatProvider? formatProvider)
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("SubRecord");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder, formatProvider))
        {
            stringBuilder.Append(" ");
        }
        stringBuilder.Append("}");
        return stringBuilder.ToString();
    }
}