1. 什么是ToString
我们知道.NET任何类型都是派生自object类型的。而object类型有一个方法:ToString。 顾名思义,就是将一个对象实例转换为一个字符串(String)。
2. 为什么要ToString
我们知道对象实例(instance)都是生存在内存的一个二进制字节。但如果我们需要将该对象实例显示出来(例如显示在控件中),那么就需要一个途径能够把对象实例转换为字符串。
3. 最简单的ToString重写
我们从上面的图片就可以看到,ToString是一个虚方法(virtual).也就代表了任何类型都可以重写该方法。不重写之前,它返回什么呢?
class Program
{
static void Main(string[] args)
{
Customer c1 = new Customer() { CustomerName = "chenxizhang", Id = 1 };
Console.Write(c1.ToString());
Console.Read();
}
}
class Customer
{
public string CustomerName { get; set; }
public int Id { get; set; }
}
我们可以看到如下的结果
也就是说,默认是将类型的完整名字输出为ToString的结果。我们可以通过反射工具看一下object这个类型的代码就很明白了
但是,很显然,如果每个类型都这么输出,那么其实对用户来说一点都不友好,或者说没有任何作用。那么该如何改变这种行为呢?答案就是可以为我们的类型重写ToString方法
class Customer
{
public string CustomerName { get; set; }
public int Id { get; set; }
public override string ToString()
{
return string.Format("Id={0},Name={1}", Id, CustomerName);
}
}
加上这个代码之后,重新执行程序就可以看到如下的效果
看起来很酷,不是吗
但是这样会有一个问题,就是说这样是不是只能有一个方法?也就是说只有一个可能性呢?如果我们想根据用户的一个选项来决定怎么输出,那么该怎么办呢?
例如,用户代码可能希望只显示CustomerName,我们该怎么办?我们很自然会想到是不是再写一个重载的方法
public string ToString(string format)
{
if (format == "NameOnly")
return CustomerName;
return ToString();
}
然后,在客户代码中就可以这样调用
Console.WriteLine(c1.ToString("NameOnly"));
这样就和谐了。
等等,事实上,是否真的要这么写呢?其实不然,.NET内置了很多所谓公用的合约(Contract),例如我们的类型要支持自定义格式化,那么就可以通过实现一个接口来完成。
class Customer:IFormattable
{
public string CustomerName { get; set; }
public int Id { get; set; }
public override string ToString()
{
return string.Format("Id={0},Name={1}", Id, CustomerName);
}
//public string ToString(string format)
//{
// if (format == "NameOnly")
// return CustomerName;
// return ToString();
//}
#region IFormattable 成员
public string ToString(string format, IFormatProvider formatProvider)
{
if (format == "NameOnly")
return CustomerName;
return ToString();
}
#endregion
}
然后,可能需要稍微修改一下调用的代码
static void Main(string[] args)
{
Customer c1 = new Customer() { CustomerName = "chenxizhang", Id = 1 };
Console.WriteLine(c1.ToString());
Console.WriteLine(c1.ToString("NameOnly",null));
Console.Read();
}
经过这样修改之后,输出的结果是一样的。但使用接口的方式肯定更加规范,也更加推荐
但你可能会有一个疑问,这个ToString方法的第二个参数是什么东东呢?这就是下一节要讲的内容:定制的格式
大家也可以再想一下,我们现在是提供了两种可能性,但如果以后又要提供其他的可能性,是不是还要回过去修改Customer这个类型呢?目前而言,是的。但这样做显然是不够灵活的。有什么办法将这个紧耦合解开吗?
4. 定制的格式化提供程序(FormatProvider)
我们可以通过提供者模式来解决该问题。 可以编写下面这样一个类型
class CustomerFormatProvider : IFormatProvider,ICustomFormatter
{
#region IFormatProvider 成员
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
return null;
}
#endregion
#region ICustomFormatter 成员
public string Format(string format, object arg, IFormatProvider formatProvider)
{
Customer c = arg as Customer;
if (arg != null)
{
if (format == "NameOnly")
return c.CustomerName;
}
return c.ToString();
}
#endregion
}
然后,我们一次性修改好Customer类型里面的那个ToString方法
#region IFormattable 成员
public string ToString(string format, IFormatProvider formatProvider)
{
ICustomFormatter formatter = formatProvider.GetFormat(typeof(ICustomFormatter)) as ICustomFormatter;
if (formatter != null)
{
return formatter.Format(format, this, formatProvider);
}
return ToString();
}
#endregion
然后,在客户代码中大致要这样调用
Console.WriteLine(c1.ToString("NameOnly", new CustomerFormatProvider()));
一点都不奇怪,主程序显示的效果仍然是一样的。但是这样就是一个比较好的设计。换而言之,如果以后我们要添加一个新的格式化规则,那么我们可以单独写一个Provider,然后再调用的时候指定即可。
5. Parse方法
我们看到ToString方法是将对象实例转化为字符串,但是反过来,有没有什么方法是将字符串转化为对象实例的呢?这个方法就是我们所谓的Parse方法。其实我们在很多地方都看到过它的身影。例如
我们如果想为自己的类型添加Parse方法,那么重点是记住几个原则
1. 该方法是静态的(static)
2. 该方法的返回值类型就是类型本身
3. 该方法的参数可以由我们随意定义,但通常都是string
从上图中也可以看到,还有一个TryParse方法,如果从完整性方面来考虑。这是很有必要的。