string.Format以及IFormattable,IFormatProvider,ICustomFormatter

看了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带有自定义格式化参数的理解

View Code
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 接口(该方法更灵活且可解决代码冗余问题)

转载于:https://www.cnblogs.com/go-go-go/archive/2012/10/25/2738741.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值