Ian*_*son 7 c# boolean string-interpolation
如何为布尔值指定与其他类型的其他格式字符串一致的格式字符串?
给出以下代码:
double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;
string result = $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}";
Run Code Online (Sandbox Code Playgroud)
我可以为每个原始类型指定一种格式,但bool看起来除外。我知道我能做到:
string result = $"{d:0.0}, {now:HH:mm}, time to party? {(isPartyTime ? "yes!" : "no")}";
Run Code Online (Sandbox Code Playgroud)
但这仍然与其他类型不一致。
有没有一种方法可以使插值字符串中的布尔值保持一致?
PS我确实搜索了包含此链接的答案:
/sf/ask/tagged/c/%23+string-interpolation+boolean
令人惊讶的是结果为零。
NPr*_*ras 10
不幸的是,不,没有。
根据 Microsoft 的说法,唯一具有格式字符串的数据类型是:
DateTime, DateTimeOffset)System.Enum)BigInteger, Byte, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64)GuidTimeSpanBoolean.ToString()只能返回“True”或“False”。它甚至说,如果需要将其写入 XML,则需要手动执行ToLowerCase()(由于缺乏字符串格式)。
(我还没有任何 C# 10.0 功能的经验,但我将来会扩展此部分 -由于我的日常工作项目依赖于 .NET Framework 4.8,现在我仍然停留在C# 7.3 领域)
Boolean包装结构如果您控制字符串格式调用站点,则只需更改bool/Boolean类型的值以使用隐式可转换的零开销值类型,例如:
public readonly struct YesNoBoolean : IEquatable<YesNoBoolean>
{
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators
public static implicit operator Boolean ( YesNoBoolean self ) => self.Value;
public static implicit operator YesNoBoolean( Boolean value ) => new MyBoolean( value );
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/true-false-operators
public static Boolean operator true( YesNoBoolean self ) => self.Value == true;
public static Boolean operator false( YesNoBoolean self ) => self.Value == false;
public YesNoBoolean( Boolean value )
{
this.Value = value;
}
public readonly Boolean Value;
public override String ToString()
{
return this.Value ? "Yes" : "No";
}
// TODO: Override Equals, GetHashCode, IEquatable<YesNoBoolean>.Equals, etc.
}
Run Code Online (Sandbox Code Playgroud)
因此,您的示例调用站点变为:
double d = Math.PI;
DateTime now = DateTime.Now;
YesNoBoolean isPartyTime = true; // <-- Yay for implicit conversion.
string result = $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}";
Run Code Online (Sandbox Code Playgroud)
并且result将会是"3.1, 21:03, time to party? Yes"
Boolean.TrueString并且FalseString因为Booleansstatic readonly String TrueString = "True";也标记为initonly您无法使用反射覆盖它,所以这样做:
typeof(Boolean).GetField( "TrueString" )!.SetValue( obj: null, value: "Yes" );
Run Code Online (Sandbox Code Playgroud)
...会给你一个运行时异常:
初始化类型“ ”后无法设置
initonly static field“ ”。TrueStringSystem.Boolean
通过操纵原始内存仍然是可能的,但这超出了这个问题的范围。
IFormatProvider和ICustomFormatter:通过提供自定义 ;始终可以覆盖内String.Format插字符串(例如)的格式化方式。虽然 while通过公开重载参数使事情变得容易,但插值字符串却不然,相反它会迫使您稍微丑化您的代码。$"Hello, {world}"IFormatProviderString.FormatFormat
IFormatProvider仍然不足。
IFormatProvider.GetFormat(Type)只能使用以下 3 个formatType参数之一调用:
typeof(DateTimeFormatInfo)typeof(NumberFormatInfo)typeof(ICustomFormatter)typeof()GetFormat魔法发生在内部ICustomFormatter.Format,实现起来也很简单:
public class MyCustomFormatProvider : IFormatProvider
{
public static readonly MyCustomFormatProvider Instance = new MyCustomFormatProvider();
public Object? GetFormat( Type? formatType )
{
if( formatType == typeof(ICustomFormatter) )
{
return MyCustomFormatter.Instance;
}
return null;
}
}
public class MyCustomFormatter : ICustomFormatter
{
public static readonly MyCustomFormatter Instance = new MyCustomFormatter();
public String? Format( String? format, Object? arg, IFormatProvider? formatProvider )
{
// * `format` is the "aaa" in "{0:aaa}"
// * `arg` is the single value
// * `formatProvider` will always be the parent instance of `MyCustomFormatProvider` and can be ignored.
if( arg is Boolean b )
{
return b ? "Yes" : "No";
}
return null; // Returning null will cause .NET's composite-string-formatting code to fall-back to test `(arg as IFormattable)?.ToString(format)` and if that fails, then just `arg.ToString()`.
}
public static MyFormat( this String format, params Object?[] args )
{
return String.Format( Instance, format: format, arg: args );
}
}
Run Code Online (Sandbox Code Playgroud)
...所以就以某种方式传递MyCustomFormatProvider.Instance,String.Format如下所示。
double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;
string result1 = String.Format( MyCustomFormatProvider.Instance, "{0:0.0}, {1:HH:mm}, time to party? {2}", d, now, isPartyTime );
// or add `using static MyCustomFormatProvider` and use `MyFormat` directly:
string result2 = MyFormat( "{0:0.0}, {1:HH:mm}, time to party? {2}", d, now, isPartyTime );
// or as an extension method:
string result3 = "{0:0.0} {1:HH:mm}, time to party? {2}".MyFormat( d, now, isPartyTime );
// Assert( result1 == result2 == result3 );
Run Code Online (Sandbox Code Playgroud)
这适用于String.Format,但我们如何使用MyCustomFormatProviderC#$""内插字符串...?
...非常困难,因为设计插值字符串功能的 C# 语言团队使其始终通过provider: null,因此所有值都使用其默认(通常特定于文化的)格式,并且他们没有提供任何方法来轻松指定自定义IFormatProvider,尽管有数十年历史的静态代码分析规则反对依赖隐式使用CurrentCulture(尽管微软打破自己的规则并不罕见......)。
CultureInfo.CurrentCulture不起作用,因为根本Boolean.ToString()不使用。CultureInfo困难源于这样一个事实:C#$""内插字符串总是隐式转换为String(即它们立即格式化),除非字符串表达式直接分配给类型为or$""的变量或参数,但令人恼火的是,这不会扩展到扩展方法(因此行不通的。FormattableStringIFormattable public static String MyFormat( this FormattableString fs, ... )
这里唯一可以做的就是将该方法作为(语法上的“正常”)静态方法调用来调用,尽管使用在某种程度上减少了人体工程学问题 - 如果您使用全局使用(需要 C# 10.0 ,它已经支持自定义插值字符串处理程序,所以这有点没有意义)。String MyFormat( this FormattableString fs, ... )using static MyFormattableStringExtensions
但像这样:
public static class MyFormattableStringExtensions
{
// The `this` modifier is unnecessary, but I'm retaining it just-in-case it's eventually supported.
public static String MyFmt( this FormattableString fs )
{
return fs.ToString( MyCustomFormatProvider.Instance );
}
}
Run Code Online (Sandbox Code Playgroud)
并像这样使用:
using static MyFormattableStringExtensions;
// ...
double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;
string result = MyFmt( $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}" );
Assert.AreEqual( result, "3.1, 23:05, time to party? Yes" );
Run Code Online (Sandbox Code Playgroud)
FormattableString参数数组MyFmt( $"" ),有一种更简单的替代方法可以实现IFormatProviderand ICustomFormatter:只需直接编辑 的FormattableString值参数数组。Boolean格式化String.Format(IFormatProvider, String format, ...).public static class MyFormattableStringExtensions
{
public static String MyFmt( this FormattableString fs )
{
if( fs.ArgumentCount == 0 ) return fs.Format;
Object?[] args = fs.GetArguments();
for( Int32 i = 0; i < args.Length; i++ )
{
if( args[i] is Boolean b )
{
args[i] = b ? "Yes" : "No";
}
}
return String.Format( CultureInfo.CurrentCulture, fs.Format, arg: args );
}
}
Run Code Online (Sandbox Code Playgroud)
并像以前一样使用以获得相同的结果:
using static MyFormattableStringExtensions;
// ...
double d = Math.PI;
DateTime now = DateTime.Now;
bool isPartyTime = true;
string result = MyFmt( $"{d:0.0}, {now:HH:mm}, time to party? {isPartyTime}" );
Assert.AreEqual( result, "3.1, 23:05, time to party? Yes" );
Run Code Online (Sandbox Code Playgroud)