I'm trying to develop a custom collection or list class which provides me the following capabilities:
Add(MyObject)
Add(MyObject, String) ' key
Remove(MyObject)
RemoveByKey(String) ' key
RemoveAt(Index)
Count
Clear
Item(Index)
Item(String) ' key
Contains(MyObject)
ContainsKey(String) ' key
GetEnumerator ' for-each MyObject
I've searched through IEnumerable, IList, ICollection but none are satisfying what I need above. For example, they're all missing storing of objects by Key(string).
How do I create such a collection/list object? I've noticed that the best thing that matches my requirements is the ListViewItemCollection object available by the system. I wish I could see the coding inside it to find out how it has implemented the storing and retrieval of objects.
Can anybody help out? Or guide me to tutorial links.
Thanks.
Example of such class could be System.Windows.Forms.Control.ControlCollection which is implemented like List<KeyValuePair<string,Control>> (actually Control already contains the key) and this[string] is implemented using ordinary for-loop (linear searching for the key).
We can help to speed this up by adding Dictionary and add every item with key to both collection (List+Dictionary). Items without key are added to List only.
EDIT: Further improvement may use List<KeyValuePair<string,T>> and Dictionary<string,KeyValuePair<int,T>> - mapping index from List to Dictionary for faster removing. RemoveAt should check if the key is preset and delete it from dictionary as well. RemoveByKey can get index for internal List.RemoveAt.
ADDON based on comments: implementation of IEnumerable<T> may look like this:
class MyObjectList<T>: IEnumerable<T> {
public IEnumerator<T> GetEnumerator() {
T[] a = items; int n = size;
for(int i = 0; i < n; i++)
yield return a[i]; }
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator(); }
...the above are internals of List<T>
ADDON: here you can see my custom ListCore and List created from it (feel free to use it as you wish).
I bet there are tons of easier ways to do this, but here's one approach. You could create a struct containing the key and value of each item:
public sealed class Listionary<K, T> : IDictionary<K, T>, IList<T>
{
private struct ListionaryPair
{
public ListionaryPair(T item) : this()
{
Item = item;
}
public ListionaryPair(K key, T item) : this()
{
Key = key;
Item = item;
}
public K Key { get; private set; }
public T Item { get; private set; }
public bool HasKey { get; private set; }
}
private readonly List<ListionaryPair> list = new List<ListionaryPair>();
(The whole HasKey thing allows value types as K, or null references as valid keys. If you only want string keys you could replace this struct with KeyValuePair<string, T>)
And then both interfaces separately:
public void Add(T item)
{
list.Add(new ListionaryPair(item));
}
public void Add(K key, T item)
{
list.Add(new ListionaryPair(key, item));
}
public void RemoveAt(int index)
{
list.RemoveAt(index);
}
You can hide ugly methods by explicitly implementing them:
void ICollection<KeyValuePair<K, T>>.CopyTo(KeyValuePair<K, T>[] array, int arrayIndex)
{
// code implementing the method
}
You'll need some helper methods for access by key:
private int IndexOfKey(K key)
{
for (int i = 0; i < list.Count; i++)
{
var pair = list[i];
if (pair.HasKey && pair.Key == key)
{
return i;
}
}
return -1;
}
but if you get them right the rest won't be that much of a challenge:
public T this[K key]
{
get
{
int index = IndexOfKey(key);
if (index < 0)
{
throw new IndexOutOfRangeException();
}
return list[index].Item;
}
set
{
int index = IndexOfKey(key);
if (index < 0)
{
throw new IndexOutOfRangeException();
}
list[index] = new ListionaryPair(key, value);
}
}
It's quite a bit of coding to complete each interface method, but most will be short and simple. You'll have to decide whether you allow multiple items with the same key, whether IDictionary<,>.Clear() clears the entire collection or only keyed items, etc.
Also there's no backing Dictionary in this example, so performance might not be that great.
Related
Given the following class:
public class GenClass<T>
{
private List<T> ItemsList {get;set;}
public Predicate<T> SomeCondition {get;set;}
public bool UsePredicate {get;set;}
public List<T> Items
{
get { //CODE Goes here; }
}
}
I need a way for the list to use the SomeConditionPredicate and return only the items than match the condition, but only if the bool UsePredicate is true. I know I can just use LINQ for this, the problem is that everytime I query with LINQ I get a different instance of an IEnumerable, and this needs to be a property, therefore I need to be able to access the same instance of the List from outside the class, because I will be adding and removing items from it, and I cannot do that with the result of a .Where, for example.
I was thinking of a custom IList<T>, but I'm not really sure how to do that.
You have a conceptual problem here. If it is the same instance, how is it supposed to filter by the condition? The reason why LINQ returns a new enumeration on every call is that it runs the query "live", and multiple queries have to be independent.
That said, you probably shouldn't have to rely on the property returning the same reference each time. If you rely on the instance being the same, what do you expect to happen when/if someone changes the predicate?
And how is adding or removing items supposed to work/act on a filtered list? If you add an item that would be filtered out, what happens?
Your question is a little unclear, but let's start with this:
public class GenClass<T>
{
private List<T> ItemsList {get;set;}
public Predicate<T> SomeCondition {get;set;}
public bool UsePredicate {get;set;}
public List<T> Items
{
get { return UsePredicate
? ItemsList.Where(SomeCondition).ToList()
: ItemsList; }
}
}
What about that doesn't work for your use case?
An IEnumerable can only be used to read the collection, but not to make changes to it. If you want to make changes to it, return an enumeration of filtered indexes instead.
public IEnumerable<int> FilteredIndexes
{
get
{
if (UsePredicate) {
return ItemsList
.Select((item, i) => i)
.Where(i => SomeCondition(ItemsList[i]));
}
return ItemsList.Select((item, i) => i);
}
}
Assuming that you have declared this indexer
public T this[int index]
{
get { return ItemsList[index]; }
set { ItemsList[index] = value; }
}
You can now use the collection like this
GenClass<string> stringCollection = new GenClass<string>();
//TODO: Add items
stringCollection.SomeCondition = s => s.StartsWith("A");
stringCollection.UsePredicate = true;
foreach (int index in stringCollection.FilteredIndexes) {
stringCollection[index] = stringCollection[index] + " starts with 'A'";
}
UPDATE
If you do not want to expose the indexes, you could create a class used as item accessor representing your collection items
public class Item<T>
{
private List<T> _items;
private int _index;
public Item(List<T> items, int index)
{
_items = items;
_index = index;
}
public T Value
{
get { return _items[_index]; }
set { _items[_index] = value; }
}
}
In your collection you would declare this property
public IEnumerable<Item<T>> FilteredItems
{
get
{
if (UsePredicate) {
return ItemsList
.Select((item, i) => new Item<T>(ItemsList, i))
.Where(item => SomeCondition(item.Value));
}
return ItemsList.Select((item, i) => new Item<T>(ItemsList, i));
}
}
Now you can use the collection like this
foreach (Item<string> item in stringCollection.FilteredItems) {
item.Value = item.Value + " starts with 'A'";
}
A general note: You can safely turn the private properties into fields. Properties are normally used as an intermediate to publicly expose field values.
Hi
i have created a Generic Array that works fine for Int,String, Float or even my Own type named Customers.
Generic Array has functions Add(), Sort(), ShowAll() thats working fine for Int, String, and even Customer Type
except when i try to showAll() method for CustomerType that shows all the values that i have added through ADD() method.
output is something like
GenericArray.Customer
not the values where as i wanted to have the values .
i have solved it through
public class GArray<T> where T : Customer
but now i cant create Generic Array of type Int,Float .
here is the ADD and ShowAll method of Class
public void Add(T temp)
{
if (index >= values.Length)
{
T[] tempArray = new T[values.Length + 1];
Array.Copy(values, tempArray, values.Length);
values = tempArray;
}
values[index] = temp;
index++;
}
public void ShowAll()
{
for (int i = 0; i < values.Length; i++)
{
Console.WriteLine(values[i]);
}
}
the values m adding
static void Main(string[] args)
{
GArray<Customer> customers = new GArray<Customer>(3);
customers.Add(new Customer(101, "xyz"));
customers.Add(new Customer(59, "abc"));
customers.ShowAll();
}
i have talked with my frnd and he said that i have to create indexer my self . can some one help me how can i create indexer in this case that works fine for customerType or any Type.
I think,If I understand the question (output is something like GenericArray.Customer, not the values where as i wanted to have the values) you should add in Customer definition:
public override string ToString()
{
// return something you want to show to identify your customer
// e.g. return Name;
return ...
}
I explain: when you use Console.WriteLine(values[i]) you tell C# to write to console Customer object... and it writes out then name of the class, as it's the default behaviour.
Defining in Customer class the default string to be converted to makes what you please...
public T this[int index]
{
get {return values[index]; }
}
I think your problem is that you have not overridden ToString in your customer class. Do that -- it will define how the objects should be displayed in the console.
Your actual problem aside for a moment, I would like to mention that there is no place for a ShowAll method in an array implementation. Why should an array be tied to a console application? Wouldn't you want to reuse it for a Windows Forms application oneday without the need to rewrite it?
Next, .NET already has a List<T> which does dynamic allocation as necessary. If you do want to write it again yourself, at least allocate the array in bigger steps (n*2 each time).
To remove the ShowAll method from the array (where it doesn't belong), you should consider taking one of the following approaches:
a) Create an extension method which works for any IEnumerable<T> (a List, Array, Collection, whatever):
public static class EnumExt
{
public static void ShowAll<T>(this IEnumerable<T> list)
{
foreach (T item in list)
Console.WriteLine(item);
}
}
Usage:
int[] array = new int[] { 1,2,3};
array.ShowAll();
b) Or, be even more abstract and create a ForEach extension method where you will pass an arbitrary delegate to perform actual work:
public static class EnumExt
{
public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (T item in list)
action(item);
}
}
Usage:
int[] array = new int[] { 1,2,3};
// now you are reusing the iterator
// for any action you want to execute
array.ForEach(Console.WriteLine);
// or
array.ForEach(item => Console.WriteLine("My item is: " + item));
When is it acceptable for an indexer to automatically add items to a collection/dictionary? Is this reasonable, or contrary to best practices?
public class I { /* snip */ }
public class D : Dictionary<string, I>
{
public I this[string name]
{
get
{
I item;
if (!this.TryGetValue(name, out item))
{
item = new I();
this.Add(name, item);
}
return item;
}
}
}
Sample of how this may be used in a collection:
public class I
{
public I(string name) {/* snip */}
public string Name { get; private set; }
/* snip */
}
public class C : Collection<I>
{
private Dictionary<string, I> nameIndex = new Dictionary<string, I>();
public I this[string name]
{
get
{
I item;
if (!nameIndex.TryGetValue(name, out item))
{
item = new I(name);
this.Add(item); // Will also add the item to nameIndex
}
return item;
}
}
//// Snip: code that manages nameIndex
// protected override void ClearItems()
// protected override void InsertItem(int index, I item)
// protected override void RemoveItem(int index)
// protected override void SetItem(int index, I item)
}
There's two problems that you should consider - both of which suggest this is a bad idea.
First, inheriting from the .NET BCL collection types is not generally a good idea. The main reason for this is that most methods on those types (like Add and Remove) are not virtual - and if you provide your own implementations in a derived class, they will not get called if you pass your collection around as the base type. In your case, by hiding the Dictionary<TK,TV> indexer property, you are creating a situation where a call using a base-class reference will do something different than a call using a derived-class reference ... a violation of the Liskov Substitution Principle:
var derived = new D();
var firstItem = derived["puppy"]; // adds the puppy entry
var base = (Dictionary<string,I>)derived;
var secondItem = base["kitten"]; // kitten WAS NOT added .. BAD!
Second, and more importantly, creating an indexer that inserts an item when you attempt to find one is entirely unexpected. Indexers have clearly defined get and set operations - implementing the get operation to modify the collection is very bad.
For the case you describe, you're much better off creating an extension method that can operate on any dictionary. Such an operation is both less surprising in what it does, and also doesn't require creating a derived collection type:
public static class DictionaryExtensions
{
public static TValue FindOrAdd<TKey,TValue>(
this IDictionary<TKey,TValue> dictionary, TKey key, TValue value )
where TValue : new()
{
TValue value;
if (!this.TryGetValue(key, out value))
{
value = new TValue();
this.Add(key, value);
}
return value;
}
}
With no other information about what you're doing, that looks like surprising behavior to me. I hope that you make it very clear from the context (i.e. name it an AutoInitializingDictionary or something) what's to be expected.
I would personally prefer to make this a method rather than an indexer; something like D.FindOrCreate. (I have the feeling there's an idiomatic name for a method that does this which I've temporarily forgotten.)
I would say this violates two principles. 1) principle of least surprise. And 2) that getters shouldn't change anything.
I wouldn't expect to add a the pair {"foo", null} if foo doesn't exist in the colleciton.
x = collection["Foo"]
I think it is perfectly fine as long as this behaviour is made perfectly clear. I have 2 decorator classes:
public class DefaultValueDictionary<K, V> : IDictionary<K, V>
{
public DefaultValueDictionary(IDictionary<K, V> baseDictionary, Func<K, V> defaultValueFunc)
{
...
}
}
and
public class ParameterlessCtorDefaultValueDictionary<K, V>
: DefaultValueDictionary<K, V> where V : new()
{
public ParameterlessCtorDefaultValueDictionary(IDictionary<K, V> baseDictionary)
: base(baseDictionary, k => new V())
{
...
}
}
The second class is perfect for counters and patterns like IDictionary<K,List<V>>;
I can do
var dict = new ParameterlessCtorDefaultValueDictionary<string, int>();
...
dict[key]++;
instead of the laborious:
int count;
if(!dict.TryGetValue(key, out count))
dict[count] = 1;
else dict[count] = count + 1;
The primary reason I would be concerned is that it wouldn't be thread-safe. Multiple readers all attempting to possibly write to the Dictionary at once would require careful lock management that you wouldn't likely think of (or get right) at first.
When is it acceptable for an indexer
to automatically add items to a
collection/dictionary?
Never
Is this reasonable, or contrary to
best practices?
Contrary to best practices
That said, if the class is named appropriately, it'd be acceptable. I'd personally use GetOrAdd instead.
I'm looking for something like a Dictionary<K,V> however with a guarantee that it preserves insertion order. Since Dictionary is a hashtable, I do not think it does.
Is there a generic collection for this, or do I need to use one of the old .NET 1.1 collections?
There is not. However, System.Collections.Specialized.OrderedDictionary should solve most need for it.
EDIT: Another option is to turn this into a Generic. I haven't tested it but it compiles (C# 6) and should work. However, it will still have the same limitations that Ondrej Petrzilka mentions in comments below.
public class OrderdDictionary<T, K>
{
public OrderedDictionary UnderlyingCollection { get; } = new OrderedDictionary();
public K this[T key]
{
get
{
return (K)UnderlyingCollection[key];
}
set
{
UnderlyingCollection[key] = value;
}
}
public K this[int index]
{
get
{
return (K)UnderlyingCollection[index];
}
set
{
UnderlyingCollection[index] = value;
}
}
public ICollection<T> Keys => UnderlyingCollection.Keys.OfType<T>().ToList();
public ICollection<K> Values => UnderlyingCollection.Values.OfType<K>().ToList();
public bool IsReadOnly => UnderlyingCollection.IsReadOnly;
public int Count => UnderlyingCollection.Count;
public IDictionaryEnumerator GetEnumerator() => UnderlyingCollection.GetEnumerator();
public void Insert(int index, T key, K value) => UnderlyingCollection.Insert(index, key, value);
public void RemoveAt(int index) => UnderlyingCollection.RemoveAt(index);
public bool Contains(T key) => UnderlyingCollection.Contains(key);
public void Add(T key, K value) => UnderlyingCollection.Add(key, value);
public void Clear() => UnderlyingCollection.Clear();
public void Remove(T key) => UnderlyingCollection.Remove(key);
public void CopyTo(Array array, int index) => UnderlyingCollection.CopyTo(array, index);
}
There actually is one, which is generic and has been around since .net 2.0. It's called KeyedCollection<TKey, TItem>. However, it comes with the restriction that it constructs the keys from the values, so it is not a generic Key/Value pair collection. (Although you can of course use it like KeyedCollection<TKey, Tuple<TKey, TItem>> as a workaround).
If you need it as an IDictionary<TKey, TItem>, it has a .Dictionary property.
A somewhat minor issue that I have with it is that it is an abstract class and you have to subclass it and implement:
protected abstract TKey GetKeyForItem(TItem item)
I'd rather just pass a lambda into the constructor for this purpose, but then again, I guess a virtual method is slightly faster than a lambda (any comments on this appreciated).
Edit As the question came up in the comments: KeyedCollection preserves order, as it inherits from Collection<T>, which does (it derives from IList<T>. See also the documentation of the Add method: Adds an object to the end of the Collection.).
There is an OrderedDictionary class that is a dictionary but can be indexed in insertion order, but it is not generified. There is not a generified one in the .Net framework at present.
I have read a comment somewhere from someone on the .Net team that said that they may implement a generified version in the future, but if so it would most likely be called IndexableDictionary instead of OrderedDictionary to make its behaviour more obvious.
EDIT: found the quote. It was on the MSDN page for OrderedDictionary, attributed to David M. Kean from Microsoft:
This type is actually misnamed; it is not an 'ordered' dictionary as such, but rather an 'indexed' dictionary. Although, today there is no equivalent generic version of this type, if we add one in the future it is likely that we will name such as type 'IndexedDictionary'.
Here is a wrapper for the non-generic Systems.Collections.Specialized.OrderedDictionary type.
This type will return keys/value/pairs sequences in insertion order, much like Ruby 2.0 hashes.
It does not require C#6 magic, conforms to IDictionary<TKey,TValue> (which also means that accessing a non-assigned key throws an exception), and ought to be serializable.
It is given the name 'IndexedDictionary' per note on Adrian's answer.
YMMV.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
/// <summary>
/// A dictionary that maintains insertion ordering of keys.
///
/// This is useful for emitting JSON where it is preferable to keep the key ordering
/// for various human-friendlier reasons.
///
/// There is no support to manually re-order keys or to access keys
/// by index without using Keys/Values or the Enumerator (eg).
/// </summary>
[Serializable]
public sealed class IndexedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
// Non-generic version only in .NET 4.5
private readonly OrderedDictionary _backing = new OrderedDictionary();
private IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs
{
get
{
return _backing.OfType<DictionaryEntry>()
.Select(e => new KeyValuePair<TKey, TValue>((TKey)e.Key, (TValue)e.Value));
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return KeyValuePairs.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_backing[item.Key] = item.Value;
}
public void Clear()
{
_backing.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _backing.Contains(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
KeyValuePairs.ToList().CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
TValue value;
if (TryGetValue(item.Key, out value)
&& Equals(value, item.Value))
{
Remove(item.Key);
return true;
}
return false;
}
public int Count
{
get { return _backing.Count; }
}
public bool IsReadOnly
{
get { return _backing.IsReadOnly; }
}
public bool ContainsKey(TKey key)
{
return _backing.Contains(key);
}
public void Add(TKey key, TValue value)
{
_backing.Add(key, value);
}
public bool Remove(TKey key)
{
var result = _backing.Contains(key);
if (result) {
_backing.Remove(key);
}
return result;
}
public bool TryGetValue(TKey key, out TValue value)
{
object foundValue;
if ((foundValue = _backing[key]) != null
|| _backing.Contains(key))
{
// Either found with a non-null value, or contained value is null.
value = (TValue)foundValue;
return true;
}
value = default(TValue);
return false;
}
public TValue this[TKey key]
{
get
{
TValue value;
if (TryGetValue(key, out value))
return value;
throw new KeyNotFoundException();
}
set { _backing[key] = value; }
}
public ICollection<TKey> Keys
{
get { return _backing.Keys.OfType<TKey>().ToList(); }
}
public ICollection<TValue> Values
{
get { return _backing.Values.OfType<TValue>().ToList(); }
}
}
I know you're writing C#, but Java has a class called LinkedHashMap that uses a private LinkedList to maintain the order of insertion of keys. If you can't find a suitable generic solution, perhaps that would be a start on implementing your own.
Another option for a Generic Key/Value pair that preserves insertion is to use something like:
Queue<KeyValuePair<string, string>>
This would be a guaranteed ordered list. You can en-queue and dequeue in an ordered faction similar to Add/Remove of dictionary as opposed to resizing an Array. It can often serve as a middle ground between a non-resizing ordered (by insertion) array and an autoresizing unordered (by insertion) list.
If you need constant complexity of Add, Remove, ContainsKey and order preservation, then there's no such generic in .NET Framework 4.5.
If you're okay with 3rd party code, take a look at my repository (permissive MIT license):
https://github.com/OndrejPetrzilka/Rock.Collections
There's OrderedDictionary<K,V> collection:
source code based on classic Dictionary<K,V> (from .NET Core)
preserves order of insertions and allows manual reordering
features reversed enumeration
has same operation complexities as Dictionary<K,V>
Add and Remove operations are ~20% slower compared to Dictionary<K,V>
consumes 8 more bytes of memory per item
Code:
//A SortedDictionary is sorted on the key (not value)
System.Collections.Generic.SortedDictionary<string, string> testSortDic = new SortedDictionary<string, string>();
//Add some values with the keys out of order
testSortDic.Add("key5", "value 1");
testSortDic.Add("key3", "value 2");
testSortDic.Add("key2", "value 3");
testSortDic.Add("key4", "value 4");
testSortDic.Add("key1", "value 5");
//Display the elements.
foreach (KeyValuePair<string, string> kvp in testSortDic)
{
Console.WriteLine("Key = {0}, value = {1}", kvp.Key, kvp.Value);
}
Output:
Key = key1, value = value 5
Key = key2, value = value 3
Key = key3, value = value 2
Key = key4, value = value 4
Key = key5, value = value 1
I have a Dictionary<int, object> where the int is a property of obj. Is there a better data structure for this? I feel like using a property as the key is redundant.
This Dictionary<int, obj> is a field in a container class that allows for random indexing into the obj values based on an int id number. The simplified (no exception handling) indexer in the container class would look like:
obj this[int id]
{
get{ return this.myDictionary[id];}
}
where myDictionary is the aforementioned Dictionary<int, obj> holding the objects.
This may be the typical way of quick random access but I wanted to get second opinions.
There's no concrete class in the framework that does this. There's an abstract one though, KeyedCollection. You'll have to derive your own class from that one and implement the GetKeyForItem() method. That's pretty easy, just return the value of the property by which you want to index.
That's all you need to do, but do keep an eye on ChangeItemKey(). You have to do something meaningful when the property that you use as the key changes value. Easy enough if you ensure that the property is immutable (only has a getter). But quite awkward when you don't, the object itself now needs to have awareness of it being stored in your collection. If you don't do anything about it (calling ChangeItemKey), the object gets lost in the collection, you can't find it back. Pretty close to a leak.
Note how Dictionary<> side-steps this problem by specifying the key value and the object separately. You may still not be able to find the object back but at least it doesn't get lost by design.
There is a KeyedCollection class.
EDIT: The KeyedCollection can use a dictionary internally, but it cleaner interface for this particular scenario than a raw dictionary since you can lookup by values directly. Admittedly I don't find it very useful in general.
You can implement your own KeyedCollection trivially if the extra overhead that comes with the factory settings isn't worth it. The original KeyedCollection in System.Collections.ObjectModel is internally a Dictionary<TKey, TItem> and a List<TItem> which means you can have operations defined on both IList<> and IDictionary<>. For e.g., you can insert, access by index, traverse collection in the inserted order (all which IList<> facilitates) and at the same time you can have quick lookups based on key (with the help of dictionary). This means that when you're adding or removing an item they have to be performed on both underlying collections, apart from the small memory overhead to hold the extra List<> (but the objects are not duplicated as such). Though the addition speeds are not affected much (List<> addition is O(1)), removal speed is affected a little.
If you don't care about insertion order and accessing by index:
public class KeyedCollection<TKey, TItem> : ICollection<TItem>
{
MemberInfo _keyInfo;
Func<TItem, TKey> _keySelector;
Dictionary<TKey, TItem> _dict;
public TItem this[TKey key]
{
get { return _dict[key]; }
}
public int Count
{
get { return _dict.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public ICollection<TKey> Keys
{
get { return _dict.Keys; }
}
private ICollection<TItem> Items
{
get { return _dict.Values; }
}
public KeyedCollection(Expression<Func<TItem, TKey>> keySelector, IEqualityComparer<TKey> comparer = null)
{
var keyExpression = keySelector.Body as MemberExpression;
if (keyExpression != null)
_keyInfo = keyExpression.Member;
_keySelector = keySelector.Compile();
_dict = new Dictionary<TKey, TItem>(comparer);
}
private TKey GetKeyForItem(TItem item)
{
return _keySelector(item);
}
public bool ContainsKey(TKey key)
{
return _dict.ContainsKey(key);
}
public bool Contains(TItem item)
{
return ContainsKey(GetKeyForItem(item));
}
public bool TryGetItem(TKey key, out TItem item)
{
return _dict.TryGetValue(key, out item);
}
public void Add(TItem item)
{
_dict.Add(GetKeyForItem(item), item);
}
public void AddOrUpdate(TItem item)
{
_dict[GetKeyForItem(item)] = item;
}
public bool UpdateKey(TKey oldKey, TKey newKey)
{
TItem oldItem;
if (_keyInfo == null || !TryGetItem(oldKey, out oldItem) || !SetItem(oldItem, newKey)) // important
return false;
RemoveKey(oldKey);
Add(oldItem);
return true;
}
private bool SetItem(TItem item, TKey key)
{
var propertyInfo = _keyInfo as PropertyInfo;
if (propertyInfo != null)
{
if (!propertyInfo.CanWrite)
return false;
propertyInfo.SetValue(item, key, null);
return true;
}
var fieldInfo = _keyInfo as FieldInfo;
if (fieldInfo != null)
{
if (fieldInfo.IsInitOnly)
return false;
fieldInfo.SetValue(item, key);
return true;
}
return false;
}
public bool RemoveKey(TKey key)
{
return _dict.Remove(key);
}
public bool Remove(TItem item)
{
return RemoveKey(GetKeyForItem(item));
}
public void Clear()
{
_dict.Clear();
}
public void CopyTo(TItem[] array, int arrayIndex)
{
Items.CopyTo(array, arrayIndex);
}
public IEnumerator<TItem> GetEnumerator()
{
return Items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
I have implemented ICollection<TItem> to make it more standard compliant - and also you get the nice collection initializer syntax! :)
A sample usage:
var p1 = new Person { Name = "a" };
var p2 = new Person { Name = "b" };
var people = new KeyedCollection<string, Person>(p => p.Name) { p1, p2 };
// p1 == people["a"];
// p2 == people["b"];
C# dynamic properties post seems to show that using a Dictionary was a popular choice. The other posts suggest using a HashTable
Dictionary vs Hashtable