ToString 的几个思考 FormatProvider

来源:互联网 发布:女友胸大什么感觉知乎 编辑:程序博客网 时间:2024/06/05 01:18

为什么要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; }
}

我们可以看到如下的结果

image

也就是说,默认是将类型的完整名字输出为ToString的结果。我们可以通过反射工具看一下object这个类型的代码就很明白了

image

但是,很显然,如果每个类型都这么输出,那么其实对用户来说一点都不友好,或者说没有任何作用。那么该如何改变这种行为呢?答案就是可以为我们的类型重写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);
    }

}

加上这个代码之后,重新执行程序就可以看到如下的效果

image

看起来很酷,不是吗

但是这样会有一个问题,就是说这样是不是只能有一个方法?也就是说只有一个可能性呢?如果我们想根据用户的一个选项来决定怎么输出,那么该怎么办呢?

例如,用户代码可能希望只显示CustomerName,我们该怎么办?我们很自然会想到是不是再写一个重载的方法

public string ToString(string format)
{
    if (format == "NameOnly")
        return CustomerName;
    return ToString();
}

 

然后,在客户代码中就可以这样调用

Console.WriteLine(c1.ToString("NameOnly"));

这样就和谐了。

image

等等,事实上,是否真的要这么写呢?其实不然,.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();

}

经过这样修改之后,输出的结果是一样的。但使用接口的方式肯定更加规范,也更加推荐

image

但你可能会有一个疑问,这个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()));

image

一点都不奇怪,主程序显示的效果仍然是一样的。但是这样就是一个比较好的设计。换而言之,如果以后我们要添加一个新的格式化规则,那么我们可以单独写一个Provider,然后再调用的时候指定即可。