Effective C# 原則5:始終提供ToString()

來源:互聯網
上載者:User

在.Net世界裡,用得最多的方法之一就是System.Object.ToStrying()了。你應該為你所有的客戶寫一個“通情達理”的類(譯註:這裡是指這個類應該對方便使用)。要麼,你就迫使所用類的使用者,去使用類的屬性並添加一些合理的易讀的說明。這個以字串形式存在,關於你設計的類的說明,可以很容易的向你的使用者顯示一些關於對象的資訊到:Windows Form裡,Web Form裡,控制台輸出。這些字元說明可以用於調試。你寫的任何一種類型,都應該合理的重寫這個方法。當你設計更多的複雜的類型時,你應該實現應變能力更強的IFormattable.ToString(). 承認這個:如果你不重寫(override)這個常規的方法,或者只是寫一個很糟糕的,你的客戶將不得不為你修正它。

System.Object版的ToString()方法只傳回型別的名字。這並沒有太多有用的資訊:“Rect”,“Point”,“Size”並不會如你所想的那樣顯示給你的使用者。但那隻是在你沒有為你的類重寫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個成員,這樣的組合是會讓人瘋掉的。推薦使用Regex來完成這樣的工作,Regex在處理文字時的表現還是很出色的。)

添加了這樣的函數後,你就讓使用者具有了可以這樣指定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 介面。因此,即使這個類的作者沒有提供合理的ToString行為,你可以自己來完成。當然,從類的外面來實現,你只能訪問公用屬性成資料來取得字串。實現兩個介面,IFormatProvider 和 IcustomFormatter, 只做一些文字輸出,並不需要很多工作。但在.Net架構裡,你所實現的指定的文字輸出在哪裡都可以得到很好的支援。

所以,再回到類的作者上來。重寫Object.ToString(),為你的類提供一些說明是件很簡單的事。你每次都應該為你的類型提供這樣的支援。而且這應該是對你的類型最顯而易見的,最常用的說明。在一些極端情況下,你的格式化不能支援一些過於靈活的輸出時,你應該借用IFormattable介面的優勢。它為你的類型進行自訂格式化輸出提供了標準方法。如果你放棄這些,你的使用者將失去用於實現自訂格式化的工具。這些解決辦法須要寫更多的代碼,並且因為你的使用者是在類的外面的,所以他們無法檢查類的裡面的狀態。

最後,大家注意到你的類型的資訊,他們會明白輸出的文字。儘可能以簡單的方式的提供這樣的資訊吧:為你的所有類型重寫ToString()方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.