使用占位符(错误)时String.Format的奇怪行为

时间:2014-08-17 09:14:53

标签: c# string string-formatting

当我了解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!"
    

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

4 个答案:

答案 0 :(得分:4)

为了清晰起见,请查看有关composite formatting的MSDN页面。

基本概要,格式项语法为:

 { index[,alignment][:formatString]}

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

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

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

答案 1 :(得分:2)

MSDN上的

String.Format article有以下说明:

  

格式项具有以下语法:{index [,alignment] [:formatString]}

     

...

     

formatString可选

     

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

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

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类型的IFormattable接口的实现中(以及可能在其他类型上),它似乎接近于“垃圾进入,垃​​圾输出”问题。 String类未实现IFormattable,因此任何格式说明符都未使用,而是调用.ToString(IFormatProvider)

同时

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

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

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

编辑 - CULPRIT:

在阅读How to see code of method which marked as MethodImplOptions.InternalCall?后,我使用了来自Shared Source Common Language Infrastructure 2.0 Release的源代码(它是.NET 2.0,但仍然是),试图找到罪魁祸首。

Number.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 var为0,因此正在调用NumberToStringFormat(&number, gc.refFormat, gc.refNumFmt);

它引导我们除了NumberToStringFormat方法中的第二个switch语句默认部分,它位于枚举每个格式字符串字符的循环中。这很简单:

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"

在某些情况下可以派上用场。

答案 2 :(得分:1)

有趣的行为确实没有下落不明。

时,您的上一个示例有效
if String.Format("My number is {0:n}!", 10)

,但在

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

这会提示您搜索MSDN上可以阅读的Standard Numeric Format Specifier文章

  

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

     

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

同一篇文章解释说:如果你有一封未被识别的单一字母,你会得到一个例外。 确实

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

如所解释的那样抛出FormatException

现在查看Custom Numeric Format Strings章节,您会找到一张符合条件的字母及其可能的混音表格,但在表格末尾您可以阅读

  

其他
  所有其他字符
  该字符将被复制到结果字符串中。

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

答案 3 :(得分:0)

结肠后放置任何你喜欢的东西是不可接受的。除了已识别的格式说明符之外的任何其他内容都可能导致异常或不可预测的行为,如您所示。我认为你不能指望string.Format在传递与正确记录的formatting types

完全不一致的参数时表现一致