string.Format中的{{{0}}}是什么意思?

55
在命名空间MS.Internal中,有一个名为NamedObject的类。
它有一段奇怪的代码块:
public override string ToString()
{
  if (_name[0] != '{')
  {
    // lazily add {} around the name, to avoid allocating a string 
    // until it's actually needed
    _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", _name);
  }

  return _name;
}

我对这个评论特别感兴趣:

    // lazily add {} around the name, to avoid allocating a string 
    // until it's actually needed
    _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", _name);

那怎么是“懒惰”的呢?它的懒惰程度如何影响其功能?


参考源码中的完整类:

//---------------------------------------------------------------------------- 
//
// <copyright file="NamedObject.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright> 
//
// Description: Placeholder object, with a name that appears in the debugger 
// 
//---------------------------------------------------------------------------

using System;
using System.Globalization;
using MS.Internal.WindowsBase;

namespace MS.Internal
{
  /// <summary> 
  /// An instance of this class can be used wherever you might otherwise use
  /// "new Object()".  The name will show up in the debugger, instead of 
  /// merely "{object}"
  /// </summary>
  [FriendAccessAllowed]   // Built into Base, also used by Framework.
  internal class NamedObject
  {
    public NamedObject(string name)
    {
      if (String.IsNullOrEmpty(name))
        throw new ArgumentNullException(name);

      _name = name;
    }

    public override string ToString()
    {
      if (_name[0] != '{')
      {
        // lazily add {} around the name, to avoid allocating a string 
        // until it's actually needed
        _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", _name);
      }

      return _name;
    }

    string _name;
  }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

如果尚未完成,则仅为_name分配一次。 - Alex K.
6
在这里,由于花括号字符串直到第一次调用ToString()才会生成,所以它被认为是懒惰的。你可以将“lazy”理解为“按需”的意思。 - Frédéric Hamidi
1
@FrédéricHamidi 哦,那是一个狡猾的小技巧。 - Mafii
3
@Mafii 我知道“懒惰”的说法听起来带有贬义,但是懒惰求值并不是任何意义上的黑客。实际上,整个编程语言都建立在这种范例之上。在这种情况下,NamedObject的实现者使用封装来保证对象的行为符合预期,并在某些情况下节省一些性能。 - KABoissonneault
@KABoissonneault 这句话并没有贬低的意思。我很喜欢惰性求值的强大之处,但是在这种情况下,它经常让我感到困惑,不知道什么时候以及为什么会发生。虽然它是隐式惰性的,并且更像是一种变通方法,但我认为这是一段很好的代码,因为它被注释了 :) - Mafii
4个回答

66

你可以用双大括号来 转义一个花括号, 即 {{ 会产生 {,而 }} 会产生 }

中间的 {0} 会像往常一样被解释 - 也就是作为对索引零参数的引用。

{{ {0} }}
^^ ^^^ ^^
|   |  |
|   |  +--- Closing curly brace
|   +------ Parameter reference
+---------- Opening curly brace

最终结果是参数零的值用花括号括起来的形式:
var res = string.Format("{{{0}}}", "hello"); // produces {hello}

“懒惰”是什么意思?

相对于这个“急切”的实现,他们称它为懒惰:

internal class NamedObject {
    public NamedObject(string name) {
        if (String.IsNullOrEmpty(name))
            throw new ArgumentNullException(name);
        if (name[0] != '{') {
            // eagerly add {} around the name
            _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", name);
        } else {
            _name = name;
        }
    }
    public override string ToString() {
        return _name;
    }
    string _name;
}

这个实现方法立即添加花括号,即使它不知道花括号中的名称将被需要。

所以字符串在if条件语句为真之前不会被初始化,因为编译器不知道它是否会被执行?(这是在欺骗编译器吗?) - Mafii
@Mafii 我用“非懒惰”的替代方案替换了解释。看起来他们想要在没有人计划调用ToString的情况下最小化创建NamedObject实例的影响。 - Sergey Kalinichenko
哦,太酷了。非常好的解释,易于理解。我甚至没有想到过那个。考虑到只有一个字符串,这真是太牛逼了。 - Mafii

15

“懒惰”是指什么?它做了什么让人觉得它是懒的?

这种懒惰体现在它之前的 if (_name[0] != '{') 上。

只有在第一次请求时,它才会改变 _name 字段。

就像其他人已经指出的那样,String.Format("{{{0}}}", _name); 应该被读作 "{{ {0} }}" 或者 "\{ {0} \}"。内部的 {0} 是要用第一个参数替换的实际字段,外部的 {{}} 是一种特殊的表示方式,可以得到单个的 {}


10

{{}} 仅代表字面上的一个左大括号 { 和右大括号 }。这种情况被称为转义大括号。

因此,如果你有 {{{0}}},并提供了参数 foo,输出将是 {foo}


6
var value = "value";
String.Format(CultureInfo.InvariantCulture, "{{{0}}}", value); // will output {value}

无论如何,我看不出与OP发布的代码有什么区别。如果他想运行它,他可以简单地运行它。 - Thomas Weller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接