Person是一個值類型數據,在存儲到ArrayList之前它被裝箱 。這會產生一個拷貝。而在移出的Persone對象上通過訪問屬性做一些修改時, 另一個拷貝被創建。而你所做的修改只是針對的拷貝,而實際上還有第三個拷貝 通過ToString()方法來訪問attendees[0]中的對象。
正因為這以及其它 一些原因,你應該創建一些恆定的值類型(參見原則7)。如果你非要在集合中使 用可變的值類型,那就使用System.Array類,它是類型安全的。
如果一 個數組不是一個合理的集合,以C#1.x中你可以通過使用接口來修正這個錯誤。 盡量選擇一些接口而不是公共的方法,來訪問箱子的內部去修改數據:
public interface IPersonName
{
string Name
{
get; set;
}
}
struct Person : IPersonName
{
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
public override string ToString( )
{
return _Name;
}
}
// Using the Person in a collection:
ArrayList attendees = new ArrayList( );
Person p = new Person( "Old Name" );
attendees.Add( p ); // box
// Try to change the name:
// Use the interface, not the type.
// No Unbox needed
(( IPersonName )attendees[ 0 ] ).Name = "New Name";
// Writes "New Name":
Console.WriteLine(
attendees[ 0 ].ToString( )); // unbox
裝箱後的引用類型會實現原數據類型上所有已經實現的接 口。這就是說,不用做拷貝,你可以通過調用箱子上的IPersonaName.Name方法 來直接訪問請求到箱子內部的值類型數據。在值類型上創建的接口可以讓你訪問 集合裡的箱子的內部,從而直接修改它的值。在值類型上實現的接口並沒有讓值 類型成為多態的,這又會引入裝箱的懲罰(參見原則20)。
在C#2.0中對泛 型簡介中,很多限制已經做了修改(參見原則49)。泛型接口和泛型集合會時同處 理好集合與接口的困境。在那之前,我們還是要避免裝箱。是的,值類型可以轉 化為System.Object或者其它任何的接口引用。這些轉化是隱式的,使得發現它 們成為繁雜的工作。這些也就是環境和語言的規則,裝箱與拆箱操作會在你不經 意時做一些對象的拷貝,這會產生一些BUG。同樣,把值類型多樣化處理會對性 能有所損失。時刻注意那些把值類型轉化成System.Object或者接口類型的地方 :把值類型放到集合裡,調用定義參數為System.Object類型的方法,或者強制 轉化為System.Object。能夠避免就盡量避免!
返回教程目錄