C#中的命名字符串格式化

156

在C#中是否有一种根据名称而不是位置格式化字符串的方法?

在Python中,我可以像这个例子一样做(从这里无耻地盗用):

>>> print '%(language)s has %(#)03d quote types.' % \
      {'language': "Python", "#": 2}
Python has 002 quote types.

有没有办法在C#中做到这一点?比如说:

String.Format("{some_variable}: {some_other_variable}", ...);

能够使用变量名来完成这个操作会更好,但是使用字典也是可以接受的。


我也觉得 Ruby 缺少这个功能。 - JesperE
我认为你的例子过于简单,会导致人们给出无用的答案。也许在字符串中多次使用一个变量会更具有示范性。 - Wedge
实际上,具体的困惑在于String.Format的使用。这导致了像我这样的答案不够有帮助,因为它们不是基于变量的,但就String.Format而言是准确的。 - John Rudy
1
调用String.Format显然是一个人为的例子。当然,除非您不知道使用省略号调用String.Format是不可能的。问题在于我没有指定我想要通过命名参数而不是位置进行格式化,这个问题已经被解决了。 - Jason Baker
FYI:已提交到MS Connect的用户反馈中心,请求将其作为框架的标准功能。对于任何感兴趣的人,请点赞:http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/5828904-named-token-string-formatting - JohnLBevan
18个回答

131

13
我一直很喜欢使用 FormatWith(),但我想指出最近遇到的一个问题。该实现依赖于 System.Web.UI 中的 DataBinder,在 SQL CLR 中不受支持。Inject(o) 不依赖于数据绑定器,这使它在我编写的 SQL CLR 对象中用于多令牌替换非常有用。 - EBarr
1
也许你可以更新一下你回答的第一句话。字符串插值在C#和VB中已经出现了几个月(终于...)。你的答案排在首位,如果你能将读者链接到一些更新的.NET资源,那么它可能会对他们有用。 - miroxlav
1
@miroxlav 这并不完全相同。您无法传递插值字符串:https://dev59.com/_lwY5IYBdhLWcg3w_L-W - DixonD
@DixonD - 你说得没错,但这不是他们的目的。在你提供的问答中,OP试图引用变量名,但在变量存在之前。这不是一个很好的想法,但如果有人坚持这样做,他可以构建专门的解析器。但我不会把这个与一般的字符串插值概念混淆。 - miroxlav

45

3
那篇文章中可以下载的代码已经出现404错误了。我也很想看看它。 - quentin-starin
2
@qes:在评论中发布了更新后的链接:http://code.haacked.com/util/NamedStringFormatSolution.zip - Oliver Salzburg
3
@OliverSalzburg :我已经使用SmartFormat作为我的格式化需求工具有一段时间了,非常喜欢。https://github.com/scottrippey/SmartFormat - quentin-starin
@qes:您是否介意可能写一个关于它的答案并展示它如何工作?看起来很有趣。 - Oliver Salzburg
@qes:你应该把SmartFormat加入答案中,因为它非常好用且得到了积极的支持(2015年)。 - Răzvan Flavius Panda
NamedFormat("{a} 不是 {b}", new {a:"我的a",b:"我的b"}); 这就像魔法一样简短。 - Fabrice T

43

内插字符串被添加到C# 6.0和Visual Basic 14中

二者都是通过新的Roslyn编译器在Visual Studio 2015中引入的。

  • C# 6.0:

    return "\{someVariable} and also \{someOtherVariable}" 或者
    return $"{someVariable} and also {someOtherVariable}"

  • VB 14:

    return $"{someVariable} and also {someOtherVariable}"

值得注意的特性(在Visual Studio 2015 IDE中):

  • 语法着色被支持——字符串中包含的变量将被高亮显示
  • 重构被支持——当重新命名时,字符串中包含的变量也会被重命名
  • 实际上不仅支持变量名,还支持表达式——例如,不仅{index}有效,而且{(index+1).ToString().Trim()}也有效。

享受吧!(并在VS中点击“发送微笑”)


2
这个问题被标记为 .net 3.5,因此您的信息是有效的,但它并不是一个替代方案。 - Douglas Gandini
1
@miroxlav - 你对于框架版本是正确的。字符串插值只取决于VS 2015中使用的新Roslyn编译器。 - Douglas Gandini
2
除非您的格式字符串放入代码本身,否则这也不起作用。即,如果您的格式字符串来自外部源,例如配置文件或数据库,则它将无法工作。 - Craig Brett

39

您还可以像这样使用匿名类型:

    public string Format(string input, object p)
    {
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
            input = input.Replace("{" + prop.Name + "}", (prop.GetValue(p) ?? "(null)").ToString());

        return input;
    }
当然,如果你想解析格式,那么需要更多的代码,但是你可以使用这个函数来格式化一个字符串,像这样:
Format("test {first} and {another}", new { first = "something", another = "something else" })

1
非常适合那些仍在使用2.0版本的人。是的,我知道……这个解决方案简单易懂。而且它有效!!! - Brad Bruce

14

似乎没有现成的解决方案。不过,实现自己的IFormatProvider 并连接到一个IDictionary 来存储值是可行的。

var Stuff = new Dictionary<string, object> {
   { "language", "Python" },
   { "#", 2 }
};
var Formatter = new DictionaryFormatProvider();

// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);

输出:

Python 有 2 种引号类型

需要注意的是,您不能混合使用 FormatProviders,因此无法同时使用花式文本格式化。


1
+1 是因为它概念最佳,而且在 http://mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html 上有很好的实现 - 其他帖子也包括这个方法,但它们还提出了基于反射的方法,我认为这些方法相当邪恶。 - Adam Ralph

9

该框架本身没有提供此功能的方法,但您可以参考 Scott Hanselman 的 这篇文章。示例用法:

Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo); 

这段代码由James Newton-King编写,类似于子属性和索引的工作方式,

string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));

James的代码依赖于System.Web.UI.DataBinder来解析字符串,并需要引用System.Web,这在非Web应用程序中有些人不喜欢。

编辑:如果您没有准备好具有属性的对象,则它们可以很好地与匿名类型一起使用:

string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });

7

4

我认为你能够接近的最好方式是使用索引格式:

String.Format("{0} has {1} quote types.", "C#", "1");

如果你愿意分多步进行,并相信你不会在字符串的其他地方找到“变量”,那么还有String.Replace()可用:

string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");

将其扩展为使用列表:

List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

您也可以使用Dictionary<string, string>,通过遍历其.Keys集合来实现此操作,但是通过使用List<KeyValuePair<string, string>>,我们可以利用List的.ForEach()方法并将其压缩为一行:

replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

一个lambda表达式可能更简单,但我还在使用.Net 2.0。另外需要注意的是,当迭代使用.Replace()时,性能并不出色,因为在.Net中字符串是不可变的。此外,这需要以某种方式定义MyString变量,使其可以被委托访问,所以它还不完美。

好吧,这不是最漂亮的解决方案,但现在我正在使用它。我唯一做的不同之处是使用 StringBuilder 而不是 string,这样我就不会不断创建新的字符串了。 - Jason Baker

3
我的开源库Regextra支持命名格式化(以及其他功能)。它目前针对.NET 4.0+,并可在NuGet上获得。我还有一篇介绍性的博客文章:Regextra:帮助您减少问题{2}。
命名格式化支持以下内容:
  • 基本格式化
  • 嵌套属性格式化
  • 字典格式化
  • 分隔符转义
  • 标准/自定义/IFormatProvider字符串格式化
示例:
var order = new
{
    Description = "Widget",
    OrderDate = DateTime.Now,
    Details = new
    {
        UnitPrice = 1500
    }
};

string template = "We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";

string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);

结果:

我们刚刚发出了您于2014年2月28日下单的“Widget”订单。您的{信用卡}将被收取1,500.00美元。

请查看项目的 GitHub 链接(上方)和维基百科以获取其他示例。


哇,这看起来很棒,特别是在处理一些更困难的格式例子时。 - Nicholas Petersen

2

请看这个:

public static string StringFormat(string format, object source)
{
    var matches = Regex.Matches(format, @"\{(.+?)\}");
    List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();

    return keys.Aggregate(
        format,
        (current, key) =>
        {
            int colonIndex = key.IndexOf(':');
            return current.Replace(
                "{" + key + "}",
                colonIndex > 0
                    ? DataBinder.Eval(source, key.Substring(0, colonIndex), "{0:" + key.Substring(colonIndex + 1) + "}")
                    : DataBinder.Eval(source, key).ToString());
        });
}

示例:

string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));

与其他解决方案相比,性能表现还不错。


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