The System.Collections.ObjectModel.KeyedCollection class is a very useful alternative to System.Col开发者_运维技巧lections.Generic.Dictionary, especially when the key data is part of the object being stored or you want to be able to enumerate the items in order. Unfortunately, the class is abstract, and I am unable to find a general concrete implementation in the core .NET framework.
The Framework Design Guidlines book indicates that a concrete implementation should be provided for abstract types (section 4.4 Abstract Class Design). Why would the framework designers leave out a general concrete implementation of such a useful class, especially when it could be provided by simply exposing a constructor that accepts and stores a Converter from the item to its key:
public class ConcreteKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
{
private Converter<TItem, TKey> getKeyForItem = null;
public ConcreteKeyedCollection(Converter<TItem, TKey> getKeyForItem)
{
if (getKeyForItem == null) { throw new ArgumentNullException("getKeyForItem"); }
this.getKeyForItem = getKeyForItem;
}
protected override TKey GetKeyForItem(TItem item)
{
return this.getKeyForItem(item);
}
}
There are concrete implementations, including (but not limited to):
- KeyedByTypeCollection<TItem> (I use this class frequently)
- MessageHeaderDescriptionCollection
To the spirit of your question, no there is no generic implementation and since I do not work for Microsoft I could only speculate. Since the concrete implementation is as easy as you've shown, I won't offer any speculation (as it would probably be wrong).
Here is the implementation I came up with
public class LookupKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
{
private Func<TItem, TKey> _getKeyFunc;
public LookupKeyedCollection(Func<TItem, TKey> getKeyFunc)
{
_getKeyFunc = getKeyFunc;
}
//Required KeyedCollection implementation
protected override TKey GetKeyForItem(TItem item)
{
return _getKeyFunc(item);
}
public bool TryGetItem(TKey key, out TItem item)
{
if (Dictionary == null)
{
item = default(TItem);
return false;
}
return Dictionary.TryGetValue(key, out item);
}
public void AddOrUpdate(TItem item)
{
Remove(_getKeyFunc(item));
Add(item);
}
public new bool Contains(TItem item)
{
return base.Contains(_getKeyFunc(item));
}
}
The reasoning behind the methods can be mostly found in the following:
- http://web.archive.org/web/20081123034725/http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic62318.aspx
- http://www.hardcodet.net/2008/03/numericallykeyedcollection
The reason that there's no concrete implementation is that it won't be serializable (you can't serialize a delegate). All collections in the BCL are serializable.
That's why it's better to inherit and override the method, especially when you can't predict how the collection is going to be used.
Here is one that I came up with. It either hard codes the property names or you can use the [Key] attribute if you'd like.
///// <summary>
///// Creates an indexed list. Requires that [Key] attribute be applied to a property in TValue object.
///// </summary>
///// <example>
///// public class Test
///// {
///// [Key]
///// public int Id { get; set; }
///// }
/////
///// IndexedList<int, Test> tests;
///// </example>
///// <typeparam name="TKey"></typeparam>
///// <typeparam name="TValue"></typeparam>
public class IndexedList<TKey, TValue> : KeyedCollection<TKey, TValue>
{
PropertyInfo keyProperty;
public IndexedList()
{
foreach (var property in typeof(TValue).GetProperties())
{
// this requires .net 4, which I couldn't use due to the WPF shadow effect deprication
//if (property.PropertyType == typeof(TKey) && property.IsDefined(typeof(KeyAttribute), true))
if (property.PropertyType == typeof(TKey) && (property.Name.ToUpper() == "ID" || property.Name.ToUpper() == "KEY"))
{
keyProperty = property;
return;
}
}
throw new ArgumentException(String.Format("Unable to find a property in {0} that is named Id or Key and is of type {1}.", typeof(TValue).Name, typeof(TKey).Name));
}
protected override TKey GetKeyForItem(TValue item)
{
return (TKey)keyProperty.GetValue(item, null);
}
}
精彩评论