Fork me on GitHub

您不能不知的ToString()方法

1.1.1 摘要

     相信大家对ToString()方法再熟悉不过了,由于该方法是.NET程序中最常用的方法之一,我们除了可以直接调用ToString()方法之外,.NET中的某些方法也隐式调用ToString()方法(WPF,Windows Form和Silverlight等)。

1.1.2 正文

      首先让我们了解一下ToString()的由来,它是由Object类提供一个虚的方法,ToString()方法返回一个字符串显示调用该方法对象的类型,Object类中实现如下。

////Object中的ToString()实现
public virtual string ToString()
{
    return this.GetType().ToString();
}

      现在我们知道Object类提供的是一个虚的ToString()方法,表明.NET也提供我们重写ToString()方法,接来下让我们定义一个Customer类,然后再使用Console.WriteLine()方法隐式调用Customer类的ToString()方法输出,注意我们现在还没有重写ToString方法,所以我们调用的是Object类的ToString()方法。

 

/// <summary>
/// Customer has three properities
/// Name, Revenum and Tel.
/// </summary>
public class Customer
{
    private string _name;
    private decimal _revenue;
    private string _tel;
    private string _zip;

    /// <summary>
    /// Initializes a new instance of the <see cref="Customer"/> class.
    /// </summary>
    public Customer(string name, decimal revenue, string tel, string zip)
    {
        this.Name = name;
        this.Revenue = revenue;
        this.Tel = tel;
        this.Zip = zip;
        
    }

    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    /// <summary>
    /// Gets or sets the revenue.
    /// </summary>
    /// <value>
    /// The revenue.
    /// </value>
    public decimal Revenue
    {
        get { return _revenue; }
        set { _revenue = value; }
    }

    /// <summary>
    /// Gets or sets the tel.
    /// </summary>
    /// <value>
    /// The tel.
    /// </value>
    public string Tel
    {
        get { return _tel; }
        set { _tel = value; }
    }

    /// <summary>
    /// Gets or sets the zip.
    /// </summary>
    /// <value>
    /// The zip.
    /// </value>
    public string Zip
    {
        get { return _zip; }
        set { _zip = value; }
    }

}
tostring1

图1输出类型字符串

重新ToString()方法

    

     通过上图我们可以很明确的说明了在没有重新ToString()方法时,我们调用的是父类Object的ToString()方法。不但它的输出不怎么make sence,而且它并没有输出我们需要的值,那么接下来让我们重写ToString()方法。

 

public override string ToString()
{
    //// Implemention Code.
}

 

    上面我们重写了ToString()方法返回Name,Revenuehe和Tel属性,接着我们使用WriteLine()方法隐式调用ToString()方法(.NET提供显式和隐式调用ToString()的方法,如:Console.WriteLine、String.Format和.NET控件等)。

    虽然简单的ToString()方法很多时候已经可以满足我们的需求,但有时候我们还需要功能更强的方法来格式化输出字符串。(如:电话号码,日期和邮编等格式)

    要实现上述格式,我们可以通过实现IFormattable 接口来解决这个问题。 IFormattable 接口包含了一个重载的ToString()方法,它允许我们为类型指定某种格式信息。当我们需要为类型创建不同形式的字符串输出时,实现这个接口就显得很有用了。

  1. 实现IFormattable接口

     

        现在我们修改一下我们的Customer类,让它实现IFormattable接口,然后重写ToString()方法,实现如下:

     

    /// <summary>
    /// Customer has three properities
    /// Name, Revenum and Tel.
    /// </summary>
    public class Customer : IFormattable
    {
        private string _name;
        private decimal _revenue;
        private string _tel;
        private string _zip;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="Customer"/> class.
        /// </summary>
        public Customer(string name, decimal revenue, string tel, string zip)
        {
            this.Name = name;
            this.Revenue = revenue;
            this.Tel = tel;
            this.Zip = zip;
            
        }
    
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    
        /// <summary>
        /// Gets or sets the revenue.
        /// </summary>
        /// <value>
        /// The revenue.
        /// </value>
        public decimal Revenue
        {
            get { return _revenue; }
            set { _revenue = value; }
        }
    
        /// <summary>
        /// Gets or sets the tel.
        /// </summary>
        /// <value>
        /// The tel.
        /// </value>
        public string Tel
        {
            get { return _tel; }
            set { _tel = value; }
        }
    
        /// <summary>
        /// Gets or sets the zip.
        /// </summary>
        /// <value>
        /// The zip.
        /// </value>
        public string Zip
        {
            get { return _zip; }
            set { _zip = value; }
        }
    
        #region IFormattable 成员
        /// <summary>
        /// Override ToString method, 
        /// and custom output string format. 
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatProvider"></param>
        /// <returns></returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (formatProvider != null)
            {
                ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter;
                if (fmt != null)
                {
                    return fmt.Format(format, this, formatProvider);
                }
            }
            switch (format)
            {
    
                case "LC":
                    return string.Format("Name: {0}\nRevenue: {1:C2}\nTel: {2}\nZip: {3}\n",
                        Name.ToLower(), Revenue, Tel.Replace("_", "-"), Zip.Replace("_", ""));
    
                case "UC":
                    return string.Format("Name: {0}\nRevenue: {1:C3}\nTel: {2}\nZip: {3}\n",
                        Name.ToUpper(), Revenue, Tel.Replace("_", ":"), Zip.Replace("_", ""));
    
                case "G":
                    return string.Format("Name: {0}\nRevenue: {1}\nTel: {2}\nZip: {3}\n",
                        Name, Revenue, Tel, Zip);
    
                default:
                    return "";
            }
    
        }
    
        #endregion
    }

    class Program
    {
        /// <summary>
        /// 客户端调用ToString()方法
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Customer objCustomer = new Customer("JK_Rush",
                8888, "0723_98765423", "65_4321");
            Console.WriteLine(objCustomer.ToString("LC", null));
            Console.ReadKey();

        }

    }
tostring3

图2 IFormattable的实现

使我们设计符合OCP

    现在我们的Customer类实现了IFormattable接口,然后重写了ToString()方法,接着提供三种格式化类型输出,OK现在我们的输出字符串有更好的自定义格式了,但用户需求的格式是在不断的变化,我们无法估计和预知用户需求的输出格式,难道每当遇到不符合用户要求的格式我们都要在switch中添加吗?我们的确可以这样做,这种做法看上去是直截了当,但这不符合设计模式的OCP原则。幸运的是.NET提供了符合OCP原则的实现方法,通过实现IFormattable接口使得实现该接口的类对StringFomat()修改关闭,当实现ICustomFormatter接口的类对StringFomat()扩展开放。

“Software entities (classes, modules, functions, etc.) should be open for extension, 

but closed for modification [Martin, p.99]”

    现在我们添加自定义类CustomFormatProvider,通过它可以扩展Customer的输出字符串的格式类型,而且避免了对Customer类的修改遵守了OCP,然后再实现接口IFormatProvider和ICustomFormatter,在Format()方法中实现字符串输出格式。

/// <summary>
/// Custom string format provider.
/// </summary>
public class CustomFormatProvider : IFormatProvider, ICustomFormatter
{
    // Methods

    #region IFormatProvider 成?员?

    /// <summary>
    /// Get format provider object.
    /// </summary>
    /// <param name="formatType"></param>
    /// <returns>The CustomFormatProvider object.</returns>
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
        {
            return new CustomFormatProvider();
        }
        return null;
    }

    #endregion

    #region ICustomFormatter 成?员?

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        Customer c = arg as Customer;
        if (c != null)
        {
            //// Custom string output format.
            switch (format)
            {
                case "custom":
                    return string.Format("Name: {0}\nRevenue: {1:C4}\nTel: {2}\nZip: {3}\n",
                        c.Name.ToUpper(), c.Revenue, c.Tel.Replace("_", ":"), c.Zip.Replace("_", "-"));
                default:
                    return "";

            }
        }
        return "";

    }

    #endregion

}
class Program
{
    /// <summary>
    /// 客户端调用ToString()方法
         /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        Customer objCustomer = new Customer("JK_Rush",
            8888, "0723_98765423", "65_4321");
        Console.WriteLine(string.Format(
          new CustomFormatProvider(), "{0:custom}", objCustomer));
        Console.ReadKey();

    }
}tostring4

图4自定义字符串格式

 

tostring5

图5自定义格式实现框架

    上面的GetFormat()方法创建一个实现ICustomFormatter接口的对象,而且通过重写Format()方法来自定义输出字符串格式,通过传递不同format参数以便指定不同格式选项。

1.1.3 总结

     不管一个类是否实现了IFormattable接口,我们都可以为其创建 IFormatProvider 和 ICustomFormatter 的实现类。因此即使一个类的原来没有提供合理的ToString()行为,我们仍然可以为其提供格式化支持。当然,作为一个类的外部访问者,我们只能通过访问其中的公有属性和数据成员来构造字符串。虽然编写格式提供者类(分别实现IFormatProvider 和 ICustomFormatter)需要很多工作,且其目的仅仅是为了得到一个字符串。但是,一旦使用了这种方式来实现我们自己定义的字符串输出,它们将在.NET 框架的各个地方得到支持。

    现在,再让我们回到类这一角色上来。重写 Object.ToString()是为类提供字符串表示的最简单方式。每当我们创建一个类型时,都要提供该方法。它应该是我们的类型最明显、最常用的一种表示。而且只有在一些比较少的情况下,当我们期望为类型提供更复杂的输出格式时,才应该实现IFormattable 接口。它为“类型的用户定制类型的字符 串输出”提供了一种标准的方式。如果我们没有做这些工作,用户就要自己来实现自定义格式化器。那样的做法需要更多的代码,因为用户处于类外,无法访问对象 的内部状态。

posted @ 2011-05-22 15:09  JK_Rush  阅读(4894)  评论(6编辑  收藏  举报