独上高楼网站
  •    你所在位置:首页 VS.netC#C#高级编程(4版)〉使用泛型创建只读集合
  • 使用泛型创建只读集合
  • 作者:abatei  文章来源:博客园  发布日期:2008-02-21  浏览次数:362
  • 打印这篇文章
  • 问题

    您希望类中的一个集合里的信息可以被外界访问,但不希望用户改变这个集合。

    解决方案

    使用ReadOnlyCollection< T>包装就很容易实现只读的集合类。例子如,Lottery类包含了中奖号码,它可以被访问,但不允许被改变:

    public class Lottery
        
    {
            
    // 创建一个列表.
            List< int> _numbers = null;
            
    public Lottery()
            
    {
                
    // 初始化内部列表
                _numbers = new List< int>(5);
                
    // 添加值
                _numbers.Add(17);
                _numbers.Add(
    21);
                _numbers.Add(
    32);
                _numbers.Add(
    44);
                _numbers.Add(
    58);
            }

            
    public ReadOnlyCollection< int> Results
            
    {
                
    // 返回一份包装后的结果
                get return new ReadOnlyCollection< int>(_numbers); }
            }

    }

     

    Lottery有一个内部的List< int>,它包含了在构造方法中被填的中奖号码。有趣的部分是它有一个公有属性叫Results,通过返回的ReadOnlyCollection< int>类型可以看到其中的中奖号码,从而使用户通过返回的实例来使用它。

    如果用户试图设置集合中的一个值,将引发一个编译错误:

    Lottery tryYourLuck = new Lottery();
        
    // 打印结果.
        for (int i = 0; i <  tryYourLuck.Results.Count; i++)
        
    {
            Console.WriteLine(
    "Lottery Number " + i + " is " + tryYourLuck.Results[i]); 
        }

        
    // 改变中奖号码!
        tryYourLuck.Results[0]=29;
        
    //最后一行引发错误:// Error 26 // Property or indexer
        
    // 'System.Collections.ObjectModel.ReadOnlyCollection< int>.this[int]'
        
    // cannot be assigned to -- it is read only
     

    讨论

    ReadOnlyCollection的主要优势是使用上的灵活性,可以在任何支持IListIList< T>的集合中把它做为接口使用。ReadOnlyCollection还可以象这样包装一般数组:

    int [] items = new int[3];
        items[
    0]=0;
        items[
    1]=1;
        items[
    2]=2;
    new ReadOnlyCollection< int>(items);
     

    这为类的只读属性的标准化提供了一种方法,并使得类库使用人员习惯于这种简单的只读属性返回类型。

    阅读参考

    查看MSDN文档中的“IList”和“Generic IList”主题。


    4.10 使用相应的泛型版本替换Hashtable

    问题

    您希望通过使用相应的泛型版本替换所有Hashtable来增强应用程序性能并使得代码更为易读。当您发现这些数据结构中存放结构体和值类型会导致装箱/拆箱操作,这就变得非常有必要了。

    解决方案

    替换所有已存在的System.Collections.Hashtable类为速度更快的System.Collections.Generic.Dictionary泛型类。

    这有一个使用System.Collections.Hashtable对象的简单例子:

    public static void UseNonGenericHashtable()
        
    {
            
    // 创建并填充一个Hashtable.
            Hashtable numbers = new Hashtable();
            numbers.Add(
    1"one");    // 键会导致装箱操作
            numbers.Add(2"two");    // 键会导致装箱操作

            
    // 在Hashtable显示所有的键/值对.
            
    // 在每次迭代中都会因为键导致一个拆箱操作
            foreach (DictionaryEntry de in numbers)
            
    {
                Console.WriteLine(
    "Key: " + de.Key + "\tValue: " + de.Value);
            }

            numbers.Clear();
    }

     

    下面是相同的代码使用了System.Collections.Generic.Dictionary< T,U>对象:

    public static void UseGenericDictionary()
        
    {
            
    // 创建并填充字典.
            Dictionary< intstring> numbers = new Dictionary< intstring>();
            numbers.Add(
    1"one");
            numbers.Add(
    2"two");
            
    // 显示字典中的所有键值对.
            foreach (KeyValuePair< intstring> kvp in numbers)
            
    {
                Console.WriteLine(
    "Key: " + kvp.Key + "\tValue: " + kvp.Value);
            }

            numbers.Clear();
    }

     

    讨论

    对于应用程序中简单的Hashtable实现,这种替换将十分容易。但有些地方需要注意,如泛型Dictionary类没有实现ICloneable接口,而Hashtable类实现了。

    4-2显示了两个类中的等价成员:

    4-2 Hashtable和泛型Dictionary类的等价成员

    Hashtable 类的成员

    泛型 Dictionary 类的相应成员

    N/A

    Comparer 属性

    Count属性

    Count属性

    IsFixedSize属性

    ((IDictionary)myDict).IsFixedSize

    IsReadOnly属性

    ((IDictionary)myDict).IsReadOnly

    IsSynchronized属性

    ((IDictionary)myDict).IsSynchronized

    Item属性

    Item属性

    Keys属性

    Keys属性

    SyncRoot属性

    ((IDictionary)myDict).SyncRoot

    Values属性

    Values属性

    Add 方法

    Add方法

    Clear方法

    Clear方法

    Clone方法

    在重载构造方法中接收一个 IDictionary< T,U> 类型

    Contains方法

    ContainsKey方法

    ContainsKey方法

    ContainsKey方法

    ContainsValue方法

    ContainsValue方法

    CopyTo方法

    ((ICollection)myDict).CopyTo(arr,0)

    Remove方法

    Remove方法

    Synchronized static方法

    lock(myDictionary.SyncRoot) {…}

    N/A

    TRyGetValue方法

     

    4-2中,并非所有的HashtableDictionary的成员都一一对应。我们从属性开始,注意,只有CountKeysValuesItem属性在两个类中都存在。为了弥补Dictionary中缺少的属性,需要把它转化为IDictionary类型。下面的代码演示了如果进行这些转换以获得缺少的属性:

    Dictionary< intstring> numbers = new Dictionary< intstring>();
        Console.WriteLine(((IDictionary)numbers).IsReadOnly);
        Console.WriteLine(((IDictionary)numbers).IsFixedSize);
        Console.WriteLine(((IDictionary)numbers).IsSynchronized);
    Console.WriteLine(((IDictionary)numbers).SyncRoot);
     

    注意,由于缺少返回一个泛型字典同步版本的代码,IsSynchronized属性将总是返回falseSyncRoot属性在被调用时总是返回相同的对象。实际上这个属性返回的是this指针。微软已经决定移除泛型集合类的创建同步包装的功能。

    做为替代,他们推荐使用lock关键字锁住整个集合或其他同步对象类型以满足您的需求。

    因为在泛型字典类中也缺少了克隆方法(实际是是因为这个类没有实现ICloneable接口),您可以转而使用重载的构造方法来接收一个IDictionary< T,U>类型:

     // 创建并填充字典.
        Dictionary< intstring> numbers = new Dictionary< intstring>();
        numbers.Add(
    1"one");
        numbers.Add(
    2"two");
        
    // 显示原字典的键/值对.
        foreach (KeyValuePair< intstring> kvp in numbers)
        
    {
            Console.WriteLine(
    "Original Key: " + kvp.Key + "\tValue: " + kvp.Value);
        }

        
    // 克隆字典对象.
        Dictionary< intstring> clonedNumbers = new Dictionary< intstring>(numbers);
        
    // 显示克隆字典中的键/值对.
        foreach (KeyValuePair< intstring> kvp in numbers)
        
    {
            Console.WriteLine(
    "Cloned Key: " + kvp.Key + "\tValue: " + kvp.Value);
    }

     

    还有两个Dictionary类中缺少的方法:ContainsCopyTo方法。Contains方法的功能在Dictionary类中很容易被实现。在Hashtable类中,Cintains方法和ContainsKey方法有相同的行为,因此您可以在Dictionary类中简单地使用ContainsKey方法来模拟Hashtable类中的Contains方法:

     // 创建和填充字典.
        Dictionary< intstring> numbers = new Dictionary< intstring>();
        numbers.Add(
    1"one");
        numbers.Add(
    2"two");
        Console.WriteLine(
    "numbers.ContainsKey(1) == " + numbers.ContainsKey(1)); 
    Console.WriteLine(
    "numbers.ContainsKey(3) == " + numbers.ContainsKey(3));
    CopyTo方法也很容易在Dictionary类中被模拟,但需要做一些额外的工作:
        
    // 创建和填充字典.
        Dictionary< intstring> numbers = new Dictionary< intstring>();
        numbers.Add(
    1"one");
        numbers.Add(
    2"two");
        
    // 显示字典中的所有键/值对.
        foreach (KeyValuePair< intstring> kvp in numbers)