.NET(C#):使用KeyedCollection类型

1. 基本使用

这个类型貌似有些不起眼,在System.Collections.ObjectModel命名空间内,它在.NET 2.0时被加入,来看他的类型层次:

image

其实,他就是一个字典,只不过字典的键是从值中获取的,看他的执行中发现内不是有一个Dictionary<TKey, TItem>字段用来保存数据,同时他也是支持比较器(IEqualityComparer<TKey>对象)的。但同时他的基类是Collection<TItem>类型,而Collection<TItem>执行了IList<T>, IList这些基本集合操作接口,所以KeyedCollection<TKey, TItem>在操作上会类似一个List<T>,T则代表KeyedCollection<TKey, TItem>中的TItem类型。这样做的结果是你可以同时使用数组的索引值或者字典的键来访问KeyedCollection的成员!

当然由于KeyedCollection<TKey, TItem>内部有一个Dictionary<TKey, TItem>,同时由于继承自Collection<TItem>,所以内部也会有一个List<TItem>,所以相对Dictionary<TKey, TItem>类型他会占用更多空间。

从上图ILSpy给出的继承类上看,这个类在WCF中有过应用。

 

在使用上,比如,我们需要创建一个基于文件对象(System.IO.FileInfo对象)的字典,字典的键就是文件的路径。此时就可以使用KeyedCollection类型。注意他是一个抽象类,因为值怎样包含键是无从得知的,所以需要执行这个抽象类的GetKeyForItem方法。仅此而已,执行了这个方法后就可以使用KeyedCollection了。(注意本例中由于文件路径是不区分大小写的,所以需要在执行时设置IEqualityComparer<string>参数)

代码:

//+ using System.Collections.ObjectModel

//+ using System.IO

class MyFileDictionary : KeyedCollection<string, FileInfo>

{

//注意文件路径是不区分大小写的,所以需要设置IEqualityComparer<string>。

public MyFileDictionary()

: base(StringComparer.OrdinalIgnoreCase)

{ }

 

//从FileInfo中返回路径作为Key。

protected override string GetKeyForItem(FileInfo item)

{

return item.FullName;

}

}

 

然后测试代码:

//加入数据

var myFileDic = new MyFileDictionary();

foreach (var fileInfo in DriveInfo.GetDrives()[0].RootDirectory.GetFiles())

myFileDic.Add(fileInfo);

 

//通过Index和Key获取值,结果当然会输出True,因为他们指向同一个对象。

Console.WriteLine(myFileDic[0] == myFileDic[myFileDic[0].FullName]);

 

 

 

2. 其他KeyedCollection变种

首先是线程安全的KeyedCollection执行。

注意:对于线程安全集合,强烈建议使用.NET 4.0中System.Collections.Concurrent命名空间内提供的高性能线程安全集合。读者可参考MSDN。

事实上在.NET 3.0后,有一个线程安全的KeyedCollection执行,它包含在WCF程序集:System.ServiceModel.dll中。引用了这个程序集后,就可以使用System.Collections.Generic命名空间内的同步的KeyedCollection类型:SynchronizedKeyedCollection<K, T>类型了,注意他和KeyedCollection一样,也是抽象类。

image

 

其次上图中还有一个需要说的类型,他是KeyedByTypeCollection<TItem>类型,他直接继承自KeyedCollection,从名字上就可以看出来,这种类型的KeyedCollection的键就是从值对象的类型中获取的。

所以他只保留了TItem,从KeyedCollection<System.Type, TItem>类型中继承。如下图:

image

 

 

 

3. 执行一个非抽象的KeyedCollection

由于KeyedCollection是抽象类,所以每次需要具体的使用,你必须去定义一个继承KeyedCollection的新类型。有些麻烦,当然,完全可以自己写一个通用的非抽象KeyedCollection类型执行。使用一个负责转换的委托字段就可以。

如下代码:

class MyKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>

{

Func<TItem, TKey> _converter;

 

public MyKeyedCollection(Func<TItem, TKey> converter, IEqualityComparer<TKey> comparer,int dictionaryCreationThreshold)

: base(comparer, dictionaryCreationThreshold)

{

if (converter == null)

{

throw new ArgumentNullException(“converter”);

}

_converter = converter;

}

 

public MyKeyedCollection(Func<TItem, TKey> converter, IEqualityComparer<TKey> comparer)

: this(converter, comparer, 0)

{ }

 

public MyKeyedCollection(Func<TItem, TKey> converter)

: this(converter, null, 0)

{ }

 

protected override TKey GetKeyForItem(TItem item)

{

return _converter(item);

}

}

 

那么对于最上面那个存储FileInfo的例子,我们就不需要专门定义新类型了,使用这个MyKeyedCollection类型就可以了,如下代码:

var myFileDic = new MyKeyedCollection<string, FileInfo>(fileInfo => fileInfo.FullName,StringComparer.OrdinalIgnoreCase);

标签