编写高质量代码改善C#程序的157个建议——建议13: 为类型输出格式化字符串

建议13: 为类型输出格式化字符串

有两种方法可以为类型提供格式化的字符串输出。一种是意识到类型会产生格式化字符串输出,于是让类型继承接口IFormattable。这对类型来 说,是一种主动实现的方式,要求开发者可以预见类型在格式化方面的要求。更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活 多变的方法,可以根据需求的变化为类型提供多个格式化器。下面就来详细介绍这两种方法。

最简单的字符串输出是为类型重写ToString方法,如果没有为类型重写该方法,默认会调用Object的ToString方法,它会返回当前类型的类型名称。但即使是重写了ToString方法,提供的字符串输出也是非常单一的,而通过实现IFormattable接口的ToString方法, 可以让类型根据用户的输入而格式化输出。如下面这个类型Person,它本身提供了属性FirstName和LastName。现在,根据中文和英文语言 地区的习惯,提供的ToString方法要支持输出“Hu Jessica”或 “Jessica Hu”,实现代码应该如下所示:

    class Person : IFormattable  
    {  
        public string IDCode { get; set; }  
        public string FirstName { get; set; }  
        public string LastName { get; set; }  
     
        //实现接口IFormattable的方法ToString  
        public string ToString(string format, IFormatProvider formatProvider)  
        {  
            switch (format)  
            {  
                case "Ch":  
                    return this.ToString();  
                case "Eg":  
                    return string.Format("{0} {1}", FirstName, LastName);  
                default:  
                    return this.ToString();  
            }  
        }  
     
        //重写Object.ToString()  
        public override string ToString()  
        {  
            return string.Format("{0} {1}", LastName, FirstName);  
        }  
    } 

调用者代码如下所示:

    Person person = new Person() { FirstName = "Jessica", LastName = "Hu",  
        IDCode = "NB123" };  
    Console.WriteLine(person);  
    Console.WriteLine(person.ToString("Ch", null));  
    Console.WriteLine(person.ToString("Eg", null)); 

输出为:

    Hu Jessica  
    Hu Jessica  
    Jessica Hu 

上面这种方法是在意识到类型会存在格式化字符串输出方面的需求时,提前为类型继承了接口IFormattable。如果类型本身没有提供格 式化输出的功能,这个时候,格式化器就派上了用场。格式化器的好处就是可以根据需求的变化,随时增加或者修改它。假设Person类如以下所示的实现。

    class Person  
    {  
        public string IDCode { get; set; }  
        public string FirstName { get; set; }  
        public string LastName { get; set; }  
    } 

针对Person的格式化器的实现为:

    class PersonFomatter : IFormatProvider, ICustomFormatter  
    {  
     
        #region IFormatProvider 成员  
     
        public object GetFormat(Type formatType)  
        {  
            if (formatType == typeof(ICustomFormatter))  
                return this;  
            else  
                return null;  
        }  
     
        #endregion  
     
        #region ICustomFormatter 成员  
     
        public string Format(string format, object arg, IFormatProvider formatProvider)  
        {  
            Person person = arg as Person;  
            if (person == null)  
            {  
                return string.Empty;  
            }  
     
            switch (format)  
            {  
                case "Ch":  
                    return string.Format("{0} {1}", person.LastName,  
                        person.FirstName);  
                case "Eg":  
                    return string.Format("{0} {1}", person.FirstName,  
                        person.LastName);  
                case "ChM":  
                    return string.Format("{0} {1} : {2}", person.LastName,  
                        person.FirstName, person.IDCode);  
                default:  
                    return string.Format("{0} {1}", person.FirstName,  
                        person.LastName);  
            }  
        }  
     
        #endregion  
    }

一个典型的格式化器应该继承接口IFormatProvider和ICustomFomatter,所以应该像下面这样调用格式化器:

    Person person = new Person() { FirstName = "Jessica", LastName = "Hu", IDCode = "NB123" };  
    Console.WriteLine(person.ToString());  
    PersonFomatter pFormatter = new PersonFomatter();  
    Console.WriteLine(pFormatter.Format("Ch", person, null));  
    Console.WriteLine(pFormatter.Format("Eg", person, null));  
    Console.WriteLine(pFormatter.Format("ChM", person, null)); 

输出为:

    ConsoleApplication4.Person  
    Hu Jessica  
    Jessica Hu  
    Hu Jessica : NB123 

本示例也演示了如果没有重写Object.ToString方法,类型会输出类型名称。

实际上,在第一个版本的Person类型中,如果对IFormattable的ToString方法稍作修改,就能让格式化输出在语法上支持更多的调用方式。注意看Person类型的最终版本中ToString方法的switch结构的default部分,如下所示。

    class Person : IFormattable  
    {  
        public string IDCode { get; set; }  
        public string FirstName { get; set; }  
        public string LastName { get; set; }  
     
        //实现接口IFormattable的方法ToString  
        public string ToString(string format, IFormatProvider formatProvider)  
        {  
            switch (format)  
            {  
                case "Ch":  
                    return this.ToString();  
                case "Eg":  
                    return string.Format("{0} {1}", FirstName, LastName);  
                default:  
                    //return this.ToString();  
                    ICustomFormatter customFormatter = formatProvider as ICustomFormatter;  
                    if (customFormatter == null)  
                    {  
                        return this.ToString();  
                    }  
                    return customFormatter.Format(format, this, null);  
            }  
        }  
     
        //重写Object.ToString()  
        public override string ToString()  
        {  
            return string.Format("{0} {1}", LastName, FirstName);  
        }  
    } 

最终,调用者的代码能够支持如下所示的语法:

    Person person = new Person() { FirstName = "Jessica", LastName = "Hu", IDCode = "NB123" };  
    Console.WriteLine(person.ToString());  
    PersonFomatter pFormatter = new PersonFomatter();  
    //第一类格式化输出语法  
    Console.WriteLine(pFormatter.Format("Ch", person, null));  
    Console.WriteLine(pFormatter.Format("Eg", person, null));  
    Console.WriteLine(pFormatter.Format("ChM", person, null));  
    //第二类格式化输出语法,更简洁  
    Console.WriteLine(person.ToString("Ch", pFormatter));  
    Console.WriteLine(person.ToString("Eg", pFormatter));  
    Console.WriteLine(person.ToString("ChM", pFormatter));  

转自:《编写高质量代码改善C#程序的157个建议》陆敏技