• Effective C# 原则5:始终提供ToString()(翻译)


    Effective C# 原则5:始终提供ToString()(部分翻译)
    Always Provide ToString()

    在.Net世界里,用得最多的方法之一就是System.Object.ToStrying()了。你应该为你所有的客户写一个“通情达理”的类(译注:这里是指这个类应该对用户友好)。要么,你就迫使所用类的用户,去使用类的属性并添加一些合理的易读的说明。这个以字符串形式存在,关于你设计的类的说明,可以很容易的向你的用户显示一些关于对象的信息到:Windows Form里,Web Form里,控制台输出。这些字符说明可以用于调试。你写的任何一种类型,都应该合理的重写这个方法。当你设计更多的复杂的类型时,你应该实现应变能力更强的IFormattable.ToString(). 承认这个:如果你不重写(override)这个常规的方法,或者只是写一个很糟糕的,你的客户将不得不为你修正它。

    System.Object版的ToString()方法只返回类型的名字。这并没有太多有用的信息:“Rect”,“Point”,“Size”并不会如你所想的那样显示给你的用户。但那只是在你没有为你的类重写ToString()方法时得到的。你只用为你的类写一次,但你的客户却会使用很多次。当你设计一个类时,多添加一点小小的工作,就可以在你或者是其他人每次使用时得到回报。
    (译注:废话!)

    ======================
    这一原则就不翻译了,看的有点郁闷。就是ToString()的几个重写版本。以及一些格式化输出。我觉得本书不应该讨论这些入门级的内容,所以只是读了一遍,就没有全部翻译。
    大家知道要重写它就行了,最好是提供几个重载版本。回头有时间再翻译这一原则的其它内容。
    给一点个人建议,一般不会在一个类的ToString上提供很多的说明,给一个名字就已经足够了,然后加一个SDK帮助。更多时候,在后面添加成员类的说明。我就在一个第三方库的ToString上看到很严谨的结构,都是在类名后面,添加一些内容和重要属性的说明。

    =========================================补译:
    让我们来考虑一个简单的需求:重写System.Object.ToString()方法。你所设计的每一个类型都应该重写ToString()方法,用来为你的类型提供一些最常用的文字说明。考虑这个Customer类以及它的三个成员(fields)(译注:一般情况,类里的fields译为成员,这是面向对象设计时的概念,而在与数据库相关的地方,则是指字段):
    public class Customer
    {
      private string   _name;
      private decimal  _revenue;
      private string   _contactPhone;
    }

    默认继承自System.Object的ToString()方法会返回"Customer"。这对每个人都不会有太大的帮助。就算ToString()只是为了在调试时使用,也应该更灵活(sophisticated)一些。你重写的ToString()方法应该返回文字说明,更像是你的用户在使用这个类一样。在Customer例子中,这应该是名字:
    public override string ToString()
    {
      return _name;
    }
    如果你不遵守这一原则里的其它意见,就按照上面的方法为你所定义的所有类型重写该方法。它会直接为每个人省下时间。
    当你负责任的为Object.ToString()方法实现了重写时,这个类的对象可以更容易的被添加到Windows Form里,Web Form里,或者打印输出。 .NET的FCL使用重载的Object.ToString()在控件中显示对象:组合框,列表框,文本框,以及其它一些控件。如果你一个Windows Form或者Web Form里添加一个Customer对象的链表,你将会得到它们的名字(以文本)显示出来(译注:而不是每个对象都是同样的类型名)。
    Syste.Console.WriteLine()和System.String.Formate()在内部(实现的方法)是一样的。任何时候,.Net的FCL想取得一个customer的字符串说明时,你的customer类型会提供一个客户的名字。一个只有三行的简单函数,完成了所有的基本需求。

    这是一个简单的方法,ToString()还可以以文字(输出的方法)满足很多用户自定义类型的需求。但有些时候,你的要求可能会更多。前面的customer类型有三个成员:名字,收入和联系电话。对System.Object.ToString()(译注:原文这里有误,掉了Object)的重写只使用了_name。你可以通过实现IFormattable(这个接口)来弥补这个不足。这是一个当你需要对外输出格式化文本时使用的接口。IFormattable包含一个重载版的ToString()方法,使用这个方法,你可以为你的类型信息指定详细的格式。这也是一个当你要产生并输出多种格式的字符串时要使用的接口。customer类就是这种情况,用户将希望产生一个报表,这个报表包含了已经表格化了的用户名和去年的收入。IFormattable.ToString()方法正合你意,它可以让用户格式化输出你的类型信息。这个方法原型的参数上一包含一个格式化字符串和一个格式化引擎:
    string System.IFormattable.ToString( string format,
      IFormatProvider formatProvider )

    你可以为你设计的类型指定要使用的格式字符串。你也可以为你的格式字符串指定关键字符。在这个customer的例子中,你可以完全可以用n来表示名字,r表示收入以及p来表示电话。这样一来,你的用户就可以随意的组合指定信息,而你则须要为你的类型提供下面这个版本的的IFormattable.ToString():

    #region IFormattable Members
    // supported formats:
    // substitute n for name.
    // substitute r for revenue
    // substitute p for contact phone.
    // Combos are supported:  nr, np, npr, etc
    // "G" is general.
    string System.IFormattable.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 "r":
          return _revenue.ToString( );
        case "p":
          return _contactPhone;
        case "nr":
          return string.Format( "{0,20}, {1,10:C}",
            _name, _revenue );
        case "np":
          return string.Format( "{0,20}, {1,15}",
            _name, _contactPhone );
        case "pr":
          return string.Format( "{0,15}, {1,10:C}",
            _contactPhone, _revenue );
        case "pn":
          return string.Format( "{0,15}, {1,20}",
            _contactPhone, _name );
        case "rn":
          return string.Format( "{0,10:C}, {1,20}",
            _revenue, _name );
        case "rp":
          return string.Format( "{0,10:C}, {1,20}",
            _revenue, _contactPhone );
        case "nrp":
          return string.Format( "{0,20}, {1,10:C}, {2,15}",
            _name, _revenue, _contactPhone );
        case "npr":
          return string.Format( "{0,20}, {1,15}, {2,10:C}",
            _name, _contactPhone, _revenue );
        case "pnr":
          return string.Format( "{0,15}, {1,20}, {2,10:C}",
            _contactPhone, _name, _revenue );
        case "prn":
          return string.Format( "{0,15}, {1,10:C}, {2,15}",
            _contactPhone, _revenue, _name );
        case "rpn":
          return string.Format( "{0,10:C}, {1,15}, {2,20}",
            _revenue, _contactPhone, _name );
        case "rnp":
          return string.Format( "{0,10:C}, {1,20}, {2,15}",
            _revenue, _name, _contactPhone );
        case "n":
        case "G":
        default:
          return _name;
      }
    }
    #endregion
    (译注:上面的做法显然不合理,要是我的对象有10个成员,这样的组合是会让人疯掉的。推荐使用正则表达式来完成这样的工作,正则表达式在处理文字时的表现还是很出色的。)

    添加了这样的函数后,你就让用户具有了可以这样指定customer数据的能力:
    IFormattable c1 = new Customer();
    Console.WriteLine( "Customer record: {0}",
      c1.ToString( "nrp", null ) );

    任何对IFormattable.ToString()的实现都要指明类型,但不管你在什么时候实现IFormattation接口,你都要注意处理大小写。首先,你必须支持能用格式化字符:“G”。其次,你必须支持两个空格式化字符:""和null。当你重载Object.ToString()这个方法时,这三个格式化字符应该返回同样的字符串。.Net的FCL经常用null来调用IFormattable.ToString()方法,来取代对Object.ToString()的调用,但在少数地方使用格式符"G"来格式化字符串,从而区别通用的格式。如果你添加了对IFormattable接口的支持,并不再支持标准的格式化,你将会破坏FCL里的字符串的自动(隐式)转换。

    IFormattable.ToString()的第二个参数是一个实现了IFormatProvider接口的对象。这个对象为用户提供了一些你没有预先设置的格式化选项(译注:简单一点,就是你可以只实现你自己的格式化选项,其它的默认由它来完成)。如果你查看一下前面IFormattable.ToString()的实现,你就会毫不犹豫的拿出不计其数的,任何你喜欢的格式化选项,而这些都是的格式化中所没有的。支持人们容易阅读的输出是很自然的事,但不管你支持多少种格式,你的用户总有一天会想要你预先没想到的格式。这就为什么这个方法的前几行要检察实现了IFormatProvider的对象,并把ICustomFormatter的工作委托给它了。

    让我们把(讨论的)焦点从类的作者转移到类的使用者上来。你发现你想要的格式化不被支持。例如,你有一个一组客户,他们的名字有的大于20个字符,并且你想修改格式化选项,让它支持50个字符长的客户名。这就是为什么IFormatProvider接口要存在。你可以设计一个实现了IFormatProvider的类,并且让它同时实现ICustomFormatter接口用于格式化输出。IFormatProvider接口定义了一个方法:GetFormat()。这个方法返回一个实现了ICustomFormatter接口的对象。由ICustomFormatter接口的指定方法来完成实际的格式化工作。下面这一对(接口)实现了对输出的修改,让它可以支持50个字符长的用户名:
    // Example IFormatProvider:
    public class CustomFormatter : IFormatProvider
    {
      #region IFormatProvider Members
      // IFormatProvider contains one method.
      // This method returns an object that
      // formats using the requested interface.
      // Typically, only the ICustomFormatter
      // is implemented
      public object GetFormat( Type formatType )
      {
        if ( formatType == typeof( ICustomFormatter ))
          return new CustomerFormatProvider( );
        return null;
      }
      #endregion

      // Nested class to provide the
      // custom formatting for the Customer class.
      private class CustomerFormatProvider : ICustomFormatter
      {
        #region ICustomFormatter Members
        public string Format( string format, object arg,
          IFormatProvider formatProvider )
        {
          Customer c = arg as Customer;
          if ( c == null )
            return arg.ToString( );
          return string.Format( "{0,50}, {1,15}, {2,10:C}",
            c.Name, c.ContactPhone, c.Revenue );
        }
        #endregion
      }
    }

    GetFormat()方法取得一个实现了ICustomFormatter接口的对象。而ICustomFormatter.Format()方法,则根据用户需求负责实际的格式化输出工作。这个方法把对象转换成格式化的字符串。你可以为ICustomFormatter.Format()定义格式化字符串,因此你可以按常规指定多重格式。FormatProvider就是一个由GetFormat()方法取得的IFormatProvider对象。

    为了满足用户的格式化要求,你必须用IFormatProvider对象明确的调用string.Format()方法:
    Console.WriteLine( string.Format( new CustomFormatter(),  "", c1 ));

    你可以设计一个类,让它实现IFormatProvider和ICustomFormatter接口,再实现或者不实现IFormattable 接口。因此,即使这个类的作者没有提供合理的ToStrying行为,你可以自己来完成。当然,从类的外面来实现,你只能访问公共属性成数据来取得字符串。实现两个接口,IFormatProvider 和 IcustomFormatter, 只做一些文字输出,并不需要很多工作。但在.Net框架里,你所实现的指定的文字输出在哪里都可以得到很好的支持。

    所以,再回到类的作者上来。重写Object.ToString(),为你的类提供一些说明是件很简单的事。你每次都应该为你的类型提供这样的支持。而且这应该是对你的类型最显而易见的,最常用的说明。在一些极端情况下,你的格式化不能支持一些过于灵活的输出时,你应该借用IFormattable接口的优势。它为你的类型进行自定义格式化输出提供了标准方法。如果你放弃这些,你的用户将失去用于实现自定义格式化的工具。这些解决办法须要写更多的代码,并且因为你的用户是在类的外面的,所以他们无法检查类的里面的状态。

    最后,大家注意到你的类型的信息,他们会明白输出的文字。尽可能以简单的方式的提供这样的信息吧:为你的所有类型重写ToString()方法。

    ===========================
    小结2:
    不得不感谢a11s.net的评论,激励我翻译完了这一原则。后面的原则我会更加认真的翻译出来。
    同时我也发现我的翻译中有不少错误,有些读者已经指出来了。感谢大家的支持,在翻译完全部后,回头整理的时候,会把这些错误一一改正的。谢谢大家。



    Item 5: Always Provide ToString()
    System.Object.ToString() is one of the most used methods in the .NET environment. You should write a reasonable version for all the clients of your class. Otherwise, you force every user of your class to use the properties in your class and create a reasonable human-readable representation. This string representation of your type can be used to easily display information about an object to users: in Windows Forms, web forms, or console output. The string representation can also be useful for debugging. Every type that you create should provide a reasonable override of this method. When you create more complicated types, you should implement the more sophisticated IFormattable.ToString(). Face it: If you don't override this routine, or if you write a poor one, your clients are forced to fix it for you.

    The System.Object version returns the name of the type. It's useless information: "Rect", "Point", "Size" is not what you want to display to your users. But that's what you get when you don't override ToString() in your classes. You write a class once, but your clients use it many times. A little more work when you write the class pays off every time you or someone else uses it.

    Let's consider the simplest requirement: overriding System.Object.ToString(). Every type you create should override ToString() to provide the most common textual representation of the type. Consider a Customer class with three fields:

    public class Customer
    {
      private string   _name;
      private decimal  _revenue;
      private string   _contactPhone;
    }

    The inherited version of Object.ToString() returns "Customer". That is never useful to anyone. Even if ToString() will be used only for debugging purposes, it should be more sophisticated than that. Your override of Object.ToString() should return the textual representation most likely to be used by clients of that class. In the Customer example, that's the name:

    public override string ToString()
    {
      return _name;
    }

    If you don't follow any of the other recommendations in this item, follow that exercise for all the types you define. It will save everyone time immediately. When you provide a reasonable implementation for the Object.ToString() method, objects of this class can be more easily added to Windows Forms controls, web form controls, or printed output. The .NET FCL uses the override of Object.ToString() to display objects in any of the controls: combo boxes, list boxes, text boxes, and other controls. If you create a list of customer objects in a Windows Form or a web form, you get the name displayed as the text. System.Console.WriteLine() and System.String.Format(), as well as ToString() internally. Anytime the .NET FCL wants to get the string representation of a customer, your customer type supplies that customer's name. One simple three-line method handles all those basic requirements.

    This one simple method, ToString(), satisfies many of the requirements for displaying user-defined types as text. But sometimes, you need more. The previous customer type has three fields: the name, the revenue, and a contact phone. The System.ToString() override uses only the name. You can address that deficiency by implementing the IFormattable interface on your type. IFormattable contains an overloaded ToString() method that lets you specify formatting information for your type. It's the interface you use when you need to create different forms of string output. The customer class is one of those instances. Users will want to create a report that contains the customer name and last year's revenue in a tabular format. The IFormattable.ToString() method provides the means for you to let users format string output from your type. The IFormattable.ToString() method signature contains a format string and a format provider:

    string System.IFormattable.ToString( string format,
      IFormatProvider formatProvider )

    You can use the format string to specify your own formats for the types you create. You can specify your own key characters for the format strings. In the customer example, you could specify n to mean the name, r for the revenue, and p for the phone. By allowing the user to specify combinations as well, you would create this version of IFormattable.ToString():

    #region IFormattable Members
    // supported formats:
    // substitute n for name.
    // substitute r for revenue
    // substitute p for contact phone.
    // Combos are supported:  nr, np, npr, etc
    // "G" is general.
    string System.IFormattable.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 "r":
          return _revenue.ToString( );
        case "p":
          return _contactPhone;
        case "nr":
          return string.Format( "{0,20}, {1,10:C}",
            _name, _revenue );
        case "np":
          return string.Format( "{0,20}, {1,15}",
            _name, _contactPhone );
        case "pr":
          return string.Format( "{0,15}, {1,10:C}",
            _contactPhone, _revenue );
        case "pn":
          return string.Format( "{0,15}, {1,20}",
            _contactPhone, _name );
        case "rn":
          return string.Format( "{0,10:C}, {1,20}",
            _revenue, _name );
        case "rp":
          return string.Format( "{0,10:C}, {1,20}",
            _revenue, _contactPhone );
        case "nrp":
          return string.Format( "{0,20}, {1,10:C}, {2,15}",
            _name, _revenue, _contactPhone );
        case "npr":
          return string.Format( "{0,20}, {1,15}, {2,10:C}",
            _name, _contactPhone, _revenue );
        case "pnr":
          return string.Format( "{0,15}, {1,20}, {2,10:C}",
            _contactPhone, _name, _revenue );
        case "prn":
          return string.Format( "{0,15}, {1,10:C}, {2,15}",
            _contactPhone, _revenue, _name );
        case "rpn":
          return string.Format( "{0,10:C}, {1,15}, {2,20}",
            _revenue, _contactPhone, _name );
        case "rnp":
          return string.Format( "{0,10:C}, {1,20}, {2,15}",
            _revenue, _name, _contactPhone );
        case "n":
        case "G":
        default:
          return _name;
      }
    }
    #endregion

    Adding this function gives your clients the capability to specify the presentation of their customer data:

    IFormattable c1 = new Customer();
    Console.WriteLine( "Customer record: {0}",
      c1.ToString( "nrp", null ) );

    Any implementation of IFormattable.ToString() isspecific to the type, but you must handle certain cases whenever you implement the IFormattable interface. First, you must support the general format, "G". Second, you must support the empty format in both variations: "" and null. All three format specifiers must return the same string as your override of the Object.ToString() method. The .NET FCL calls IFormattable.ToString() instead of Object.ToString() for every type that implements IFormattable. The .NET FCL usually calls IFormattable.ToString() with a null format string , but a few locations use the "G" format string, to indicate the general format. If you add support for the IFormattable interface and do not support these standard formats, you've broken the automatic string conversions in the FCL.

    The second parameter to IFormattable.ToString() is an object that implements the IFormatProvider interface. This object lets clients provide formatting options that you did not anticipate. If you look at the previous implementation of IFormattable.ToString(), you will undoubtedly come up with any number of format options that you would like but that you find lacking. That's the nature of providing human-readable output. No matter how many different formats you support, your users will one day want some format that you did not anticipate. That's why the first few lines of the method look for an object that implements IFormatProvider and delegate the job to its ICustomFormatter.

    Shift your focus now from class author to class consumer. You find that you want a format that is not supported. For example, you have customers whose names are longer than 20 characters, and you want to modify the format to provide a 50-character width for the customer name. That's why the IFormatProvider interface is there. You create a class that implements IFormatProvider and a companion class that implements ICustomFormatter to create your custom output formats. The IFormatProvider interface defines one method: GetFormat().GetFormat() returns an object that implements the ICustomFormatter interface. The ICustomFormatter interface specifies the method that does the actual formatting. The following pair creates modified output that uses 50 columns for the customer name:

    // Example IFormatProvider:
    public class CustomFormatter : IFormatProvider
    {
      #region IFormatProvider Members
      // IFormatProvider contains one method.
      // This method returns an object that
      // formats using the requested interface.
      // Typically, only the ICustomFormatter
      // is implemented
      public object GetFormat( Type formatType )
      {
        if ( formatType == typeof( ICustomFormatter ))
          return new CustomerFormatProvider( );
        return null;
      }
      #endregion

      // Nested class to provide the
      // custom formatting for the Customer class.
      private class CustomerFormatProvider : ICustomFormatter
      {
        #region ICustomFormatter Members
        public string Format( string format, object arg,
          IFormatProvider formatProvider )
        {
          Customer c = arg as Customer;
          if ( c == null )
            return arg.ToString( );
          return string.Format( "{0,50}, {1,15}, {2,10:C}",
            c.Name, c.ContactPhone, c.Revenue );
        }
        #endregion
      }
    }

    The GetFormat() method creates the object that implements the ICustomFormatter interface. The ICustomFormatter.Format() method does the actual work of formatting the output in the requested manner. That one method translates the object into a string format. You can define the format strings for ICustomFormatter.Format() so that you can specify multiple formats in one routine. The FormatProvider will be the IFormatProvider object from the GetFormat() method.

    To specify your custom format, you need to explicitly call string.Format() with the IFormatProvider object:

    Console.WriteLine( string.Format( new CustomFormatter(),
      "", c1 ));

    You can create IFormatProvider and ICustomFormatter implementations for classes whether or not the class implemented the IFormattable interface. So, even if the class author didn't provide reasonable ToString() behavior, you can make your own. Of course, from outside the class, you have access to only the public properties and data members to construct your strings. Writing two classes, IFormatProvider and IcustomFormatter, is a lot of work just to get text output. But implementing your specific text output using this form means that it is supported everywhere in the .NET Framework.

    So now step back into the role of class author again. Overriding Object.ToString() is the simplest way to provide a string representation of your classes. You should provide that every time you create a type. It should be the most obvious, most common representation of your type. On those rarer occasions when your type is expected to provide more sophisticated output, you should take advantage of implementing the IFormattable interface. It provides the standard way for users of your class to customize the text output for your type. If you leave these out, your users are left with implementing custom formatters. Those solutions require more code, and because users are outside of your class, they cannot examine the internal state of the object.

    Eventually, people consume the information in your types. People understand text output. Provide it in the simplest fashion possible: Override ToString() in all your types.
     

  • 相关阅读:
    获取窗口句柄的几个办法
    我做的第一个网站
    学生信息管理系统
    程序题
    java冒泡排序
    模拟售票窗口,用4个线程模拟4个窗口在售票,共有8张票,用线程同步来实现
    设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1,写出程序。
    windows server 2012将计算机、回收站、文档等图标添加到桌面
    Javascript金额转化
    eclipse 每次切换工作空间都要重新配置
  • 原文地址:https://www.cnblogs.com/WuCountry/p/649708.html
Copyright © 2020-2023  润新知