程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則5:始終提供ToString()

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

編輯:關於C#

在.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()方法。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved