字符串的奇怪行为.当(错误地)使用占位符时格式化

本文关键字:占位符 格式化 错误 字符串 | 更新日期: 2023-09-27 18:18:14

当我了解String.Format函数时,我错误地认为在冒号之后命名占位符是可以接受的,所以我写了这样的代码:

String.Format("A message: '{0:message}'", "My message");
//output: "A message: 'My message'"

我刚刚意识到冒号后面的字符串是用来定义占位符的格式的,而不能像我那样用来添加注释。

但是,冒号后面的字符串显然是用作占位符的,如果:

  1. 我想用一个整数和
  2. 填充占位符
  3. 我在冒号
  4. 后面使用了无法识别的格式化字符串

但这并不能向我解释,为什么冒号后面的字符串用于占位符,如果我提供一个整数。

一些例子:

//Works for strings
String.Format("My number is {0:number}!", "10")
//output: "My number is 10!"
//Works without formating-string
String.Format("My number is {0}!", 10)
//output: "My number is 10!"
//Works with recognized formating string
String.Format("My number is {0:d}!", 10)
//output: "My number is 10!"
//Does not work with unrecognized formating string
String.Format("My number is {0:number}!", 10)
//output: "My number is number!"

为什么处理字符串和整数有区别?为什么回退输出格式化字符串而不是给定的值?

字符串的奇怪行为.当(错误地)使用占位符时格式化

请查看MSDN关于复合格式的页面。

一个基本的摘要,格式项语法为:

 { index[,alignment][:formatString]}

所以:冒号之后出现的是formatString。查看MSDN页面的"格式字符串组件"部分,了解预定义的格式字符串类型。您将而不是看到System。列表中提到的字符串。这并不奇怪,字符串已经"格式化",并且只会按原样出现在输出中。

复合格式对错误相当宽容,当您指定非法格式字符串时,它不会抛出异常。从您得到的输出中可以很明显地看出您使用的是不合法的。最重要的是,方案是可扩展的。你实际上可以使:message格式字符串合法,一个类可以实现ICustomFormatter接口来实现自己的自定义格式。这当然不会发生在System上。字符串,您不能修改该类。

所以这是预期的。如果您没有得到预期的输出,那么这很容易调试,您只需考虑两个错误。调试器消除了一个(错误的参数),你的眼睛消除了另一个。

字符串。MSDN上的格式文章有如下说明:

格式项的语法如下:{index[,alignment][:formatString]}

formatString可选

指定对象格式的字符串对应参数的结果字符串。如果省略formatString,则调用相应参数的无参数ToString方法生成它的字符串表示形式。如果指定formatString,则格式项引用的参数必须实现iformatable接口。

如果我们直接使用iformatable对值进行格式化,我们将得到相同的结果:

String garbageFormatted = (10 as IFormattable).ToString("garbage in place of int",  
    CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted); // Writes the "garbage in place of int"

所以它似乎是接近于"垃圾进,垃圾出"的东西;在Int32类型(可能还有其他类型)上实现iformatable接口的问题。String类不实现IFormattable,因此任何格式说明符都不使用,而调用.ToString(IFormatProvider)

Ildasm显示Int32.ToString(String, INumberFormat)内部调用

 string System.Number::FormatInt32(int32,
     string,
     class System.Globalization.NumberFormatInfo)

但是它是internalcall方法(extern在本机代码中某处实现),所以如果我们想确定问题的根源,Ildasm是没有用的。

编辑-罪犯:

阅读后如何看到标记为MethodImplOptions.InternalCall方法的代码?我使用了共享源公共语言基础设施2.0版本的源代码(它是。net 2.0,但尽管如此),试图找到一个罪魁祸首。

号码代码。FormatInt32位于...'sscli20'clr'src'vm'comnumber.cpp文件中。

罪魁祸首可以从FCIMPL3(Object*, COMNumber::FormatInt32, INT32 value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)的格式切换语句的默认部分推断出来:

default:
    NUMBER number;
    Int32ToNumber(value, &number);
    if (fmt != 0) {
      gc.refRetString = NumberToString(&number, fmt, digits, gc.refNumFmt);
      break;
    }
    gc.refRetString = NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);
    break;

fmt变量为0,因此正在调用NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);

它将我们引向NumberToStringFormat方法中的第二个switch语句default部分,该部分位于枚举每个格式字符串字符的循环中。很简单:

default:
    *dst++ = ch;

它只是简单地将格式字符串中的每个字符复制到输出数组中,这就是格式字符串在输出中重复结束的方式。

从一个角度来看,它允许真正使用垃圾格式字符串,不会输出任何有用的内容,但从另一个角度来看,它允许您使用以下内容:

String garbageFormatted = (1234 as IFormattable).ToString("0 thousands and ### in thousand",
    CultureInfo.CurrentCulture.NumberFormat);
Console.WriteLine(garbageFormatted); 
// Writes the "1 thousands and 234 in thousand"

确实很有趣,但并非没有原因。

if String.Format("My number is {0:n}!", 10)

,但当

返回到观察到的行为
if String.Format("My number is {0:nu}!", 10)`. 

这提示在MSDN上搜索有关标准数字格式说明符的文章,您可以在其中阅读

标准数字格式字符串用于格式化普通数字类型。标准数字格式字符串采用Axx的形式,其中:

A是一个单一的字母字符,称为格式说明符。任何包含多个字母的数字格式字符串字符(包括空白)被解释为自定义数字格式字符串。有关详细信息,请参见自定义数字格式字符串。

同一篇文章解释说:如果你有一个不被识别的字母,你会得到一个异常。事实上

if String.Format("My number is {0:K}!", 10)`. 

抛出FormatException,如前所述。

现在看看自定义数字格式字符串一章,你会发现一个符合条件的字母和它们可能的混合的表格,但是在表格的末尾你可以读到


其他所有其他字符
字符被原封不动地复制到结果字符串中。

所以我认为你已经创建了一个不能以任何方式打印该数字的格式字符串,因为没有有效的格式说明符,其中数字10应该被"格式化"。

不可以在冒号后面放置任何东西。如果输入的不是公认的格式说明符,可能会导致异常或不可预测的行为,正如您所演示的那样。我不认为你能指望绳子。当你传递的参数与文档中的格式类型完全不一致时