您不能不知的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; } } }
图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()方法,它允许我们为类型指定某种格式信息。当我们需要为类型创建不同形式的字符串输出时,实现这个接口就显得很有用了。
-
实现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(); } }
图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(); } }
图4自定义字符串格式
图5自定义格式实现框架
上面的GetFormat()方法创建一个实现ICustomFormatter接口的对象,而且通过重写Format()方法来自定义输出字符串格式,通过传递不同format参数以便指定不同格式选项。
1.1.3 总结
不管一个类是否实现了IFormattable接口,我们都可以为其创建 IFormatProvider 和 ICustomFormatter 的实现类。因此即使一个类的原来没有提供合理的ToString()行为,我们仍然可以为其提供格式化支持。当然,作为一个类的外部访问者,我们只能通过访问其中的公有属性和数据成员来构造字符串。虽然编写格式提供者类(分别实现IFormatProvider 和 ICustomFormatter)需要很多工作,且其目的仅仅是为了得到一个字符串。但是,一旦使用了这种方式来实现我们自己定义的字符串输出,它们将在.NET 框架的各个地方得到支持。
现在,再让我们回到类这一角色上来。重写 Object.ToString()是为类提供字符串表示的最简单方式。每当我们创建一个类型时,都要提供该方法。它应该是我们的类型最明显、最常用的一种表示。而且只有在一些比较少的情况下,当我们期望为类型提供更复杂的输出格式时,才应该实现IFormattable 接口。它为“类型的用户定制类型的字符 串输出”提供了一种标准的方式。如果我们没有做这些工作,用户就要自己来实现自定义格式化器。那样的做法需要更多的代码,因为用户处于类外,无法访问对象 的内部状态。
关于作者:[作者]:
JK_Rush从事.NET开发和热衷于开源高性能系统设计,通过博文交流和分享经验,欢迎转载,请保留原文地址,谢谢。 |