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();
}
}
我目前正在尝试熟悉 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();
}
}