看了MSDN解读得头晕目炫还是不理解,百度一下找到这篇好文章:喆_喆博客《彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter》网址:http://kb.cnblogs.com/kb/69729/。以下来自该文章的摘抄,其中代码有自己试机。难得能解读到这样的好文章,分析得非常清楚!
1.Format方法的内部解析方式和原理
var name = "Zhezhe"; var msg = string.Format("Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.", name, DateTime.Now, DateTime.Now.DayOfWeek); var msg1 = "Hello Cnblogs, I am " + name + ",Today is " + DateTime.Now.ToString("yyyy-MM-dd") + " " + DateTime.Now.DayOfWeek + "."; Console.WriteLine(msg); Console.WriteLine(msg1);
输入的两个字符串都是:
解析:
Format方法在取到第一个参数"Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}."之后便将其分解成多个部分:
① "Hello Cnblogs, I am " ② "{0}" ③",Today is " ④"{1:yyyy-MM-dd}"⑤ " " ⑥ "{2}"⑦ "." ——(不理解⑤空格字符串哪来的,是两个{}部分的连接字符串?无关紧要理解其思想)
分解原则:
是按照{}配对的数量进行的,{}是微软定义好的标记,你自己也可以去实现个用 []表示都无所谓。既然{}已被定义为特殊的标记,所以自己需要在字符串中包含大括号的话就必须进行转义,这个转义也和我们平时使用的"/"转义表示法不同,需使用两个大括号进行转义如 {{ 或者 }}。 如:var msg2 =string.Format("Hello {{}},I am {0}", name);
分解完毕:
分解完毕之后使用 StringBuilder的Append方法将各个部分添加进去,最后再用ToString方法转成string,其实现原理非常类似于下面的代码:
var s = new StringBuilder(); s.Append("Hello Cnblogs, I am "); s.Append(name); s.Append(",Today is "); s.Append(DateTime.Now.ToString("yyyy-MM-dd")); s.Append(" "); s.Append(DateTime.Now.DayOfWeek); s.Append("."); var msg3 = s.ToString();
在用 Append方法进行添加的时候会有两种情况:一种是{0},{1}这样的不带有特殊格式化的则直接会调用该对象的ToString方法,比如上面的 s.Append(DateTime.Now.DayOfWeek);其实就是 s.Append(DateTime.Now.DayOfWeek.ToString());在.net中,如果是自己定义的类,并且没有重写ToString方法,则会输出类的全名;另一种是{0:yyyy-MM-dd}带有特殊格式化的则继续分解,将冒号后面的内容分解出来,并且在调用ToString时作为参数传入,如上面的s.Append(DateTime.Now.ToString("yyyy-MM-dd"))
注意:string.Format()中参数个数小于序号的实际数量,错误;参数个数大于序号的实际数量,多出的参数忽略不计,其中序号的顺序不一定必须是0,1,2,3,4可以任意排列,且还能跳跃,但是中间跳跃过的序号参数里必须有,但是序号永远和第二个参数(实质是数组)的索引一致;如:
var msg4 = string.Format("Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.", name, DateTime.Now);//error! var msg4 = string.Format("Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd}.", name, DateTime.Now,DateTime.Now.DayOfWeek); var msg5 =string.Format("Hello Cnblogs, I am {0},Today is {2:yyyy-MM-dd} {3}.", name, "test", DateTime.Now, DateTime.Now.DayOfWeek);//序号跳跃
链接知识:
string和StringBuilder的区别:string虽然也是引用类型,但是该类型.net内部进行了特殊处理,让其表现出和值类型相似的特征,特别是在每次变动之后就会重新分配内存空间,而StringBuilder就不会,所以如果有很多个字符串相加拼接,则string性能较低。
2.ToString方法的深入理解
如果某个对象需要转换成ToString,并且没有手动调用该方法,程序会自动调用该方法, 这个是微软让你少些代码而已,好的习惯是始终写上 .ToString();
.net中的任何对象都具有该方法,因为该方法在object对象中定义,任何类或者结构都会继承object
ToString重载方法
public class PersonWithToString { public string Name { get; set; } public override string ToString() { return Name; } }
var msg7 =string.Format("Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.",
new PersonWithToString(){ Name ="Zhezhe" }, DateTime.Now, DateTime.Now.DayOfWeek);
Console.WriteLine(msg7);
输入结果为 输出就正常了,自己重写的方法起作用了。
总结:对自己定义的类始终重写 ToString方法。 这样在 string.Format 中或者其他需要程序自动转换成string类型时不会出现 输出类全名的情况。
ToString带有自定义格式化参数的理解
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var msg9 = string.Format("Hello Cnblogs, I am {0},Today is {1:yyyy-MM-dd} {2}.",new PersonWithToString() { Name = "Zhezhe" }.ToString("UPP"), DateTime.Now, DateTime.Now.DayOfWeek); var msg8 = string.Format("Hello Cnblogs, I am {0:UPP},Today is {1:yyyy-MM-dd} {2}.", new PersonWithToString() { Name = "Zhezhe" }, DateTime.Now, DateTime.Now.DayOfWeek); Console.WriteLine(msg9); Console.WriteLine(msg8); Console.ReadKey(); } } public class PersonWithToString { public string Name { get; set; } public override string ToString() { return Name; } public string ToString(string format) { switch (format) { case "UPP": return Name.ToUpper(); case "LOW": return Name.ToLower(); default: return Name; } } } }
输入结果:,并不是我们所期望(两个输入的名字都大写).{0:UPP}这样的格式实际上内部处理的是和 {0}一样的效果了.原因{0:UPP} 真正调用的方法签名是 IFormattable 接口的方法string ToString(string format,IFormatProvider formatProvider),如果类没有实现IFormattable接口,那么{0:UPP} 类似这种带参的会调用重载的或者是基类的ToString()方法
小结:实现IFormattable 接口的类:
①如果format参数为null(即不带格式化参数的情况,如{0})则应该调用重载的 ToString()方法,而不应该自己去另外写代码。
②{X}不带参数或者{X:YYYYYYY}带参的也不会去调用重载的或者是基类的ToString()方法了,它始终是去调用 接口定义的 ToString方法
③{X:YYYYYYY}带参达到期望的,实现该接口
无实现IFormattable接口的类:始终调用重载的或者是基类的ToString()方法
3、继续了解 IFormatProvider 和 ICustomFormatter 接口
上面的第二点我们必须得为每个类单独去实现IFormattable接口才能实现自定义的格式化参数。在一些场后还是觉得不太方便或者说代码冗余。解决方法:.net的string.Format静态方法还提供了重载方法,具体签名如下:public static string Format(IFormatProvider provider,string format,params Object[] args)。此方法的优点是不需要为后面的参数对象实现 IFormattable 接口就可以使用自定义的格式化参数。即:
需提供了实现IFormatProvider接口的类1以及实现ICustomFormatter接口的类2.
执行过程过是: 根据new类1对象object1得到类2对象object2,然后利用object2对象的Format方法进行格式化。
使用:在string.Format()方法中传递一个类1对象即可
例子:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var msg15 = string.Format(new MyHelloFormatProvider(), "{0} {1}",
new Rectangle(){ Name = "MyRectangle", Width = 14.3, Height = 10 }, new Square(){ Name = "MySquare", Side = 24.2 }); var msg16 = string.Format(new MyHelloFormatProvider(), "{0} {1}",
new Rectangle(){ Name = "MyRectangle", Width = 14.3, Height = 10 }.ToString(), new Square() { Name = "MySquare", Side = 24.2 }.ToString()); var msg17 = string.Format(new MyHelloFormatProvider(), "{0:AAA} {1:BBB}",
new Rectangle() { Name = "MyRectangle", Width = 14.3, Height = 10 }, new Square() { Name = "MySquare", Side = 24.2 });
var msg18 = string.Format(new MyHelloFormatProvider(), "{0:UPP} {1:LOW}",
new Rectangle() { Name = "MyRectangle", Width = 14.3, Height = 10 }, new Square() { Name = "MySquare", Side = 24.2 }); Console.WriteLine(msg15); Console.WriteLine(msg16); Console.WriteLine(msg17); Console.WriteLine(msg18); Console.ReadKey(); } } public class Square { public string Name { get; set; } public double Side { get; set; } public override string ToString() { return string.Format("{0}(Side:{1})",Name,Side); } } public class Rectangle { public string Name { get; set; } public double Width { get; set; } public double Height { get; set; } public override string ToString() { return string.Format("{0}(Width:{1},Height:{2})",Name,Width,Height); } } public class MyHelloFormatProvider : IFormatProvider { #region IFormatProvider Members public object GetFormat(Type formateType) { return new MyHelloFormat(); } #endregion } public class MyHelloFormat : ICustomFormatter { #region ICustomFormatter Members public string Format(string format,object arg,IFormatProvider formatProvider) { var t = "hello"; switch (format) { case "UPP": t = t.ToUpper()+"_UPP"; break; case "LOW": t = t.ToLower()+"_Low"; break; default: break; } return t + arg.ToString(); } #endregion } }
前三个输出结果都是一样的:Hello MyRectangle(Width:14.3,Height:10) Hello MySquare(Side:24.2) ,最后一行msg18带参的按期望执行。这样就不用每个类都要实现IFormattable接口才能实现自定义的格式化参数,就算以后增加了圆形,三角形等等,也都能用我们已经定义好的 MyHelloFormatProvider 和 MyHelloFormatter 去进行格式化。
疑问:如果 MyHelloFormatProvider 的 GetFormat返回的不是一个实现了 ICustomFormatter 接口的对象又会是什么情况呢?答案是会报异常。 如果返回的是 null 呢? 答案是直接调用了对象的ToString()方法了。
总结:1、每个类尽量重载ToString方法
2、自定义格式化参数
两种方法:①实现IFormattable接口
②IFormatProvider 和 ICustomFormatter 接口(该方法更灵活且可解决代码冗余问题)