编写高质量代码改善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个建议》陆敏技

时间: 2024-10-09 22:08:35

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

编写高质量代码改善C#程序的157个建议——建议45:为泛型类型参数指定逆变

建议45:为泛型类型参数指定逆变 逆变是指方法的参数可以是委托或者泛型接口的参数类型的基类.FCL4.0中支持逆变的常用委托有: Func<int T,out TResult> Predicate<in T> 常用委托有: IComparer<in T> 下面例子演示了泛型类型参数指定逆变所带来的好处: class Program { static void Main() { Programmer p = new Programmer { Name = "Mi

编写高质量代码改善C#程序的157个建议——建议27:在查询中使用Lambda表达式

建议27:在查询中使用Lambda表达式 LINQ实际上是基于扩展方法和Lambda表达式的.任何LINQ查询都能通过扩展方法的方式来代替. var personWithCompanyList = from person in personList select new { PersonName = person.Name, CompanyName = person.CompanyID==0?"Micro":"Sun" }; foreach (var item in

编写高质量代码改善C#程序的157个建议——建议26:使用匿名类型存储LINQ查询结果

建议26:使用匿名类型存储LINQ查询结果 从.NET3.0开始,C#开始支持一个新特性:匿名类型.匿名类型有var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 即支持简单类型也指出复杂类型.简单类型必须是一个非空初始值,复杂类型则是一个以new开头的初始化项. 匿名类型的属性是只读的,没有属性设置器,它一旦被初始化就不可更改. 如果两个匿名类型的属性值相同,那么就认为这两个匿名类型相等. 匿名类型可以再循环中用作初始化器. 匿名类型支持智能感知. 匿名

编写高质量代码改善C#程序的157个建议——建议20:使用泛型集合代替非泛型集合

建议20:使用泛型集合代替非泛型集合 在建议1中我们知道,如果要让代码高效运行,应该尽量避免装箱和拆箱,以及尽量减少转型.很遗憾,在微软提供给我们的第一代集合类型中没有做到这一点,下面我们看ArrayList这个类的使用情况: ArrayList al=new ArrayList(); al.Add(0); al.Add(1); al.Add("mike"); foreach (var item in al) { Console.WriteLine(item); } 上面这段代码充分演

编写高质量代码改善C#程序的157个建议——建议12: 重写Equals时也要重写GetHashCode

建议12: 重写Equals时也要重写GetHashCode 除非考虑到自定义类型会被用作基于散列的集合的键值:否则,不建议重写Equals方法,因为这会带来一系列的问题. 如果编译上一个建议中的Person这个类型,编译器会提示这样一个信息: “重写 Object.Equals(object o)但不重写 Object.GetHashCode()” 如果重写Equals方法的时候不重写GetHashCode方法,在使用如FCL中的Dictionary类时,可能隐含一些潜在的Bug.还是针对上一

编写高质量代码改善C#程序的157个建议——建议90:不要为抽象类提供公开的构造方法

建议90:不要为抽象类提供公开的构造方法 首先,抽象类可以有构造方法.即使没有为抽象类指定构造方法,编译器也会为我们生成一个默认的protected的构造方法.下面是一个标准的最简单的抽象类: abstract class MyAbstractClass { protected MyAbstractClass(){} } 其次,抽象类的方法不应该是public或internal的.抽象类设计的本意是让子类继承,而不是用于生成实例对象的.如果抽象类是public或internal的,它对于其它类型

编写高质量代码改善C#程序的157个建议——建议85:Task中的异常处理

建议85:Task中的异常处理 在任何时候,异常处理都是非常重要的一个环节.多线程与并行编程中尤其是这样.如果不处理这些后台任务中的异常,应用程序将会莫名其妙的退出.处理那些不是主线程(如果是窗体程序,那就是UI主线程)产生的异常,最终的办法都是将其包装到主线程上. 在任务并行库中,如果对任务运行Wait.WaitAny.WaitAll等方法,或者求Result属性,都能捕获到AggregateException异常.可以将AggregateException异常看做是任务并行库编程中最上层的异

编写高质量代码改善C#程序的157个建议——建议89:在并行方法体中谨慎使用锁

建议89:在并行方法体中谨慎使用锁 除了建议88所提到的场合,要谨慎使用并行的情况还包括:某些本身就需要同步运行的场合,或者需要较长时间锁定共享资源的场合. 在对整型数据进行同步操作时,可以使用静态类Interlocked的Add方法,这就极大地避免了由于进行原子操作长时间锁定某个共享资源所带来的同步性能损耗.回顾建议83中的例子. static void Main(string[] args) { int[] nums = new int[] { 1, 2, 3, 4 }; int total

编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型

建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面的限制并不是很严格,所以像下面这样的代码,在WinForm中大部分情况下还能运行(本建议后面会详细解释为什么会出现这种现象): private void buttonStartAsync_Click(object sender, EventArgs e) { Task t = new Task(()