views:

104

answers:

2

I find myself often faced with this problem: I have a dictionary where the key is some simple numeric ID and the value is an object. The ID is also contained in a specific property of that value object.

Then, I want to be able to deserialize some (format-inflexible) XML that looks like:

<listitem>
    <id>20359</id>
    <someotherval>foo</someotherval>
</listitem>
<listitem>
    ...

This requires me to deserialize with a List<V>, and it's inconvenient to have to manually convert that to a Dictionary<K,V>.

The second issue is with binding. Binding lists require that the source implements ICollection (if I remember correctly), and again it's a pain to have to manually create a new List<V> and populate it from the Dictionary<K,V>.

My current, fairly ugly but functional solution is to have the following classes:

public abstract class Keyed<KeyType>
{
 public KeyType key { get; set; }
}

public class KeyedDictionary<KeyType, ValueType> :
 Dictionary<KeyType, ValueType>
 where ValueType : Keyed<KeyType>
{
 // ...
}

public class KeyedList<KeyType, ValueType> :
 IList<ValueType>,
 System.Collections.IList
 where ValueType : Keyed<KeyType>
{
 public readonly KeyedDictionary<KeyType, ValueType> dict =
  new KeyedDictionary<KeyType, ValueType>();

 // ...
}

This works, but it's internally large and ugly. Are there any better ways?


EDIT: Here is the solution I've settled on.

public interface IKeyed<KeyType>
{
    KeyType Key { get; }
}

public class KeyedList<KeyType, ValueType> :
    KeyedCollection<KeyType, ValueType>
    where ValueType : IKeyed<KeyType>
{
    protected override KeyType GetKeyForItem(ValueType item) { return item.Key; }
}
+3  A: 

It sounds like the built-in KeyedCollection<K,I> type might do the trick. It's an abstract class so you'll need to derive your own concrete subclass(es), but that's easy enough.

You could create separate specialised implementations tailored to your exact needs, or you could create a single general-purpose version that accepts a key selector delegate as a constructor argument. (The general-purpose version will be marginally less efficient than a specialised version due to the cost of the delegate invocation each time there's a key lookup.)

var myKeyedByIdCollection =
    new ProjectionKeyedCollection<int, MyCustomType>(i => i.Id);

// ...

public class ProjectionKeyedCollection<TKey, TItem>
    : KeyedCollection<TKey, TItem>
{
    private readonly Func<TItem, TKey> _keySelector;

    public ProjectionKeyedCollection(Func<TItem, TKey> keySelector)
    {
        if (keySelector == null)
            throw new ArgumentNullException("keySelector");

        _keySelector = keySelector;
    }

    protected override TKey GetKeyForItem(TItem item)
    {
        return _keySelector(item);
    }
}
LukeH
Good answer, except for one thing : this generic collection can be serialized to XML, but not deserialized, because it has no parameterless constructor. Two options to work around that: 1. Wrap the collection in a class that initializes it; 2. Derive a specialized version of the class that provides a parameterless constructor
Thomas Levesque
Thanks! I wish I had known about KeyedCollection over a year ago! I'll paste my implementation in the question.
Reinderien
A: 

What about just deserializing to a List<something> and then use .ToDictionary() on that list? That doesn't seem too inconvenient.

recursive
It's not ideal, because then after any operations on the dictionary I'd have to do the reverse .ToList() in order to do binding or serialization. Ideally instead of having to keep a dictionary and a list synchronized they'd be one and the same object.
Reinderien