C#中的命名字符串格式
有什么方法可以通过名称而不是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}", ...);
能够使用变量名来做到这一点会很好,但是字典也是可以接受的。
解决方案
我认为最接近的是索引格式:
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); }
我们也可以通过循环访问它的.Keys集合来使用Dictionary <string,string>来做到这一点,但是通过使用List <KeyValuePair <string,string >>,我们可以利用List的.ForEach()方法并将其压缩回单线:
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});
Lambda甚至会更简单,但是我仍然使用.Net 2.0。还要注意,由于.Net中的字符串是不可变的,因此当迭代使用.Replace()时性能并不佳。同样,这要求以一种可以被委托访问的方式定义" MyString"变量,因此它还不是完美的。
我怀疑这是否可能。首先想到的是如何访问局部变量名?
但是,可能有一些使用LINQ和Lambda表达式的巧妙方法。
string language = "Python"; int numquotes = 2; string output = language + " has "+ numquotes + " language types.";
编辑:
我应该说的是:"不,我不相信C#支持我们想要做的事情。这与我们将要实现的目标非常接近。"
没有内置的方法来处理此问题。
这是一种方法
string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);
这是另一个
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);
菲尔·哈克(Phil Haack)的第三种改进方法,部分基于上述两种方法
似乎没有开箱即用的方法。但是,实现自己的IFormatProvider看起来可行,该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 has 2 quote types
需要注意的是,我们不能混用FormatProviders
,因此不能同时使用花哨的文本格式。
框架本身没有提供执行此操作的方法,但是我们可以看一下Scott Hanselman的这篇文章。用法示例:
Person p = new Person(); string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}"); Assert.AreEqual(".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 });
我有一个刚刚在这里发布到我的博客上的实现:http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx
它通过大括号转义解决了其他实现所遇到的一些问题。该帖子有详细信息。它也执行DataBinder.Eval的事情,但是仍然非常快。
参见https://stackoverflow.com/questions/271398?page=2#358259
使用链接到扩展名,我们可以编写以下代码:
var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());
然后我们将获得" foo 2 System.Object"。
我们还可以使用如下匿名类型:
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" })