不要在公共API中使用ArrayList或List ,要優先使用Collection 作為返回值


最近在閱讀Framework Design Guidelines,本着現學現用的原則,於是就用FxCop工具對代碼進行規范性檢查時,發現了很多問題,其中包括命名以及一些設計上的規范。

Do not expose generic list

其中,Do not expose generic lists 這條設計規范引起了我的注意。該規范指出“不要在對象模型中對外暴露List<T>,應該考慮使用Collection<T>,ReadOnlyCollection<T>或者KeyedCollection<K,V>,List<T>是原先ArrayList的泛型實現,是最基礎的、性能最好和功能最強大的“動態數組”,對性能進行了優化,但是相對較“封閉”,入口較多。比如,如果獎List<T>對象返回給客戶端,那么就不能實現諸如當客戶端對該集合進行更改進行通知的功能”

本文首先討論Collection和List泛型的區別,使用場景,然后演示了一個使用Collection對象作為類的屬性的例子,並展現了如何在Collection的操作中觸發事件。

Collection<T>和List<T>的主要區別和使用場景

剛開始還不太理解這個設計規范的意思,於是查了下資料,在Why we don’t recommend using List<T> in public APIs 一文中,簡要介紹了原因:

  • List<T>類型並不是為可擴展性而設計的,他優化了性能,但是丟失了可擴展性。比如,它沒有提供任何可以override的成員。這樣就不能獲得諸如集合改變時獲取通知的功能。Collection<T>集合允許我們重寫受保護的SetItem方法,這樣當我們向集合中添加或者修改集合中的記錄,調用SetItem方法的時候就可以自定義一些事件來通知對象。
  • List<T>對象有太多的與“場景”不相關的屬性和成員,將其作為成員類型對外暴露在一些情況下顯得過“重”,比如在WindowsForm中ListView.Items並沒有返回一個List對象,而是一個 ListViewItemCollection對象,該對象的簽名為:
// Summary:
//     Represents the collection of items in a System.Windows.Forms.ListView control
//     or assigned to a System.Windows.Forms.ListViewGroup.
[ListBindable(false)]
public class ListViewItemCollection : IList, ICollection, IEnumerable

是一個實現ICollection接口的對象。

還有在我們常用的DataTable的Rows對象是一個DataRowCollection對象,該對象繼承自InternalDataCollectionBase

// Summary:
//Represents a collection of rows for a System.Data.DataTable.
public sealed class DataRowCollection : InternalDataCollectionBase
public class InternalDataCollectionBase : ICollection, IEnumerable

而InternalDataCollectionBase則實現ICollection接口。

public class InternalDataCollectionBase : ICollection, IEnumerable

可以看到微軟的.NET BCL中沒有直接暴露List類型的成員。該規則建議我們使用Collection<T>。

List<T>通常用來作為類的內部實現,因為它對性能進行過優化,具有一些豐富的功能,而Collection<T>則是提供了更多的可擴展性。在編寫公共API的時候,我們應該避免接受或者返回List<T>類型的對象,而是使用List的基類或者Collection接口。

Collection<T>雖然可以直接使用,但是通常作為自定義集合的基類來使用,一般的我們應該使用Collection<T>類型的對象來對外暴露功能,除非需要一些List<T>中特有的屬性。

實踐

這里舉個簡單的例子來說明,我們有一個Person表示客戶信息的類,然后這個類中有個Addresses屬性,該屬性表示該客戶的住址,通常地址有很多個,比如公司地址,住宅地址等等。一般的,我們的設計如下。

public class Person
{
    private List<Address> addresses = new List<Address>();
<span style="color: blue;">public </span><span style="color: #2b91af;">List</span>&lt;<span style="color: #2b91af;">Address</span>&gt; Addresses
{
    <span style="color: blue;">get </span>{ <span style="color: blue;">return </span>addresses; }
}

}

假設這個是我們對外提供的一個API的一個類。那么就違反了之前討論的這一原則,不應該把成員以List<T>的形式對外暴露。可以初步修改為:

public class Person
{
    private Collection<Address> addresses = new Collection<Address>();
<span style="color: blue;">public </span><span style="color: #2b91af;">Collection</span>&lt;<span style="color: #2b91af;">Address</span>&gt; Addresses
{
    <span style="color: blue;">get </span>{ <span style="color: blue;">return </span>addresses; }
}

}

現在,假設有一個需求,當用戶的地址發生改變了,需要通知另外一個系統,給用戶發提醒,或者其他操作。現在就需要在集合發生改變的時候對外提供事件提醒了,比如當用戶修改地址后,需要發郵件通知用戶是否需要修改信用卡電子賬單寄送地址。

現在我們需要自定義我們的AddressCollection如:

public class AddressCollection : Collection<Address>
{
    public event EventHandler<AddressChangedEventArgs> AddressChanged;
<span style="color: blue;">protected override void </span>InsertItem(<span style="color: blue;">int </span>index, <span style="color: #2b91af;">Address </span>item)
{
    <span style="color: blue;">base</span>.InsertItem(index, item);

    <span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt; temp = AddressChanged;
    <span style="color: blue;">if </span>(temp != <span style="color: blue;">null</span>)
    {
        temp(<span style="color: blue;">this</span>, <span style="color: blue;">new </span><span style="color: #2b91af;">AddressChangedEventArgs</span>(<span style="color: #2b91af;">ChangeType</span>.Added, item, <span style="color: blue;">null</span>));
    }
}

<span style="color: blue;">protected override void </span>SetItem(<span style="color: blue;">int </span>index, <span style="color: #2b91af;">Address </span>item)
{
    <span style="color: #2b91af;">Address </span>replaced = Items[index];
    <span style="color: blue;">base</span>.SetItem(index, item);

    <span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt; temp = AddressChanged;
    <span style="color: blue;">if </span>(temp != <span style="color: blue;">null</span>)
    {
        temp(<span style="color: blue;">this</span>, <span style="color: blue;">new </span><span style="color: #2b91af;">AddressChangedEventArgs</span>(<span style="color: #2b91af;">ChangeType</span>.Replaced, replaced, item));
    }
}

<span style="color: blue;">protected override void </span>RemoveItem(<span style="color: blue;">int </span>index)
{
    <span style="color: #2b91af;">Address </span>removedItem = Items[index];
    <span style="color: blue;">base</span>.RemoveItem(index);

    <span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt; temp = AddressChanged;
    <span style="color: blue;">if </span>(temp != <span style="color: blue;">null</span>)
    {
        temp(<span style="color: blue;">this</span>, <span style="color: blue;">new </span><span style="color: #2b91af;">AddressChangedEventArgs</span>(<span style="color: #2b91af;">ChangeType</span>.Removed, removedItem, <span style="color: blue;">null</span>));
    }
}

<span style="color: blue;">protected override void </span>ClearItems()
{
    <span style="color: blue;">base</span>.ClearItems();

    <span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt; temp = AddressChanged;
    <span style="color: blue;">if </span>(temp != <span style="color: blue;">null</span>)
    {
        temp(<span style="color: blue;">this</span>, <span style="color: blue;">new </span><span style="color: #2b91af;">AddressChangedEventArgs</span>(<span style="color: #2b91af;">ChangeType</span>.Cleared, <span style="color: blue;">null</span>, <span style="color: blue;">null</span>));
    }
}

}

public class AddressChangedEventArgs : EventArgs
{
    public readonly Address ChangeItem;
    public readonly ChangeType ChangeType;
    public readonly Address ReplaceWith;

    public AddressChangedEventArgs(ChangeType changeType, Address item, Address replacement)
    {
        ChangeType = changeType;
        ChangeItem = item;
        ReplaceWith = replacement;
    }
}

public enum ChangeType
{
    Added,
    Removed,
    Replaced,
    Cleared
};

我們重寫了InsertItem, SetItem, RemoveItem, ClearItems這四個方法,並且在這個四個方法中Raise了事件。

現在,我們的Person類變為:

public class Person
{
    private AddressCollection addresses;
    public event EventHandler<AddressChangedEventArgs> AddressChanged;
<span style="color: blue;">public </span>Person()
{
    addresses = <span style="color: blue;">new </span><span style="color: #2b91af;">AddressCollection</span>();
    addresses.AddressChanged += <span style="color: blue;">new </span><span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt;(addresses_Changed);
    
}

<span style="color: blue;">public </span><span style="color: #2b91af;">Collection</span>&lt;<span style="color: #2b91af;">Address</span>&gt; Addresses
{
    <span style="color: blue;">get </span>{ <span style="color: blue;">return </span>addresses; }
}

<span style="color: blue;">void </span>addresses_Changed(<span style="color: blue;">object </span>sender, <span style="color: #2b91af;">AddressChangedEventArgs </span>e)
{
    <span style="color: #2b91af;">EventHandler</span>&lt;<span style="color: #2b91af;">AddressChangedEventArgs</span>&gt; temp = AddressChanged;
    <span style="color: blue;">if </span>(temp != <span style="color: blue;">null</span>)
    {
        temp(<span style="color: blue;">this</span>, e);
    }
}

<span style="color: blue;">public void </span>AddAddress(<span style="color: #2b91af;">Address </span>address)
{
    addresses.Add(address);
}

}

我們在Person類中,定義了一個私有的之前重寫的AddressCollection類表示用戶的地址集合的字段addresses,然后通過Get方法定義了一個只讀的Collection<Address>集合,該集合內部返回addresses。

在Person類中,我們提供一個AddressChanged事件來通知用戶地址信息發生改變。在Person的構造函數中,我們初始化addresses類,然后注冊AddressChanged事件,在該事件中,我們直接再次調用Person類暴露給用戶的AddressChanged事件。

最后在Persion類中添加了一個AddAddress方法,該方法調用address的Add方法。該方法里面就會觸發事件。

在使用的時候,我們只需要實例化一個Person對象,然后注冊地址修改變化的事件,這樣當我們添加地址的時候,就會觸發事件通知了。

static void Main(string[] args)
{
    Person p = new Person();
    p.AddressChanged += new EventHandler<AddressChangedEventArgs>(p_AddressChanged);
    p.AddAddress(new Address { Street= "南京東路100號" });
    Console.ReadKey();
}

static void p_AddressChanged(object sender, AddressChangedEventArgs e)
{
switch (e.ChangeType)
{
case ChangeType.Added:
Console.WriteLine("Address: Add {0}", e.ChangeItem.Street);
break;
case ChangeType.Removed:
Console.WriteLine("Address: Removed {0}", e.ChangeItem.Street);
break;
case ChangeType.Replaced:
Console.WriteLine("Address: Replaced {0} with {1}", e.ChangeItem, e.ReplaceWith);
break;
case ChangeType.Cleared:
Console.WriteLine("Address: clear address ");
break;
default:
break;
}
}

總結

本文簡要介紹了Framework Design Guidelines 中的 Do not expose generic lists 這條設計規范, 一般我們在設計通用的系統框架的API的時候遵循這一規范使得系統具有更好的擴展性。

轉載來源:https://www.cnblogs.com/yangecnu/archive/2014/05/23/Do-not-expose-generic-lists.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM