1

I would like to create a "List" of items which contains unique Type keys, keyed by the type of the item itself. I created a collection that holds a Dictionary<Type, V> and manages it.

internal class TypeCollection<V>
{
    public TypeCollection()
    {
        items = new Dictionary<Type, V>();
    }

    private Dictionary<Type, V> items;

    public void Add<T>(T value) where T : V
    {
        items.Add(typeof(T), value);
    }

    public void Remove(Type type)
    {
        items.Remove(type);
    }

    public bool TryGetValue<T>(out T value) where T : V
    {
        if (items.TryGetValue(typeof(T), out V foundValue))
        {
            value = (T)foundValue;
            return true;
        }

        value = default(T);
        return false;
    }
}

I have to iterate through the values. A for-loop is not possible, because I have to access a value by its type but a foreach-loop can do the job. I implemented the IEnumerable interface

TypeCollection<V> : IEnumerable<V>

and added the required interface methods

    public IEnumerator<V> GetEnumerator()
    {
        foreach (V value in items.Values)
        {
            yield return value;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

When I want to remove all the values from that collection I would have to implement this

    public void Clear()
    {
        items.Clear();
    }

As you might have noticed I was going to reinvent a Dictionary and why should I do that...

I created this

internal class TypeCollection<V> : Dictionary<Type, V>
{
    public void Add<T>(T value) where T : V
    {
        Add(typeof(T), value);
    }

    public bool TryGetValue<T>(out T value) where T : V
    {
        if (TryGetValue(typeof(T), out V foundValue))
        {
            value = (T)foundValue;
            return true;
        }

        value = default(T);
        return false;
    }
}

but I am not able to override the default Add and TryGetValue method. I would always have both methods, Add and Add<> so what is the "cleanest" way? I would like to hide the default Add and TryGetValue methods because there is no need to use them anymore.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 4
    Have you looked at the system type [`System.Collections.Generic.KeyedByTypeCollection`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.keyedbytypecollection-1?view=netframework-4.7.2)? – dbc Dec 13 '18 at 08:22
  • How about simply `public class TypeCollection : Dictionary { }` ? – Lasse V. Karlsen Dec 13 '18 at 08:27
  • 1
    The `KeyedByTypeCollection` is exactly what you need. You could also use a `HashSet` with a custom `IEqualityComparer` that compares the objects type – Ofir Winegarten Dec 13 '18 at 08:35
  • 1
    See: [Uses of KeyedByTypeCollection in .Net?](https://stackoverflow.com/q/330387). – dbc Dec 13 '18 at 08:35
  • @dbc if you want you can create an answer and I will mark it –  Dec 13 '18 at 08:36

4 Answers4

3

Rather than creating your own custom TypeCollection<TValue>, you can use the existing KeyedByTypeCollection<TItem> in System.Collections.Generic:

KeyedByTypeCollection<TItem> Class

Provides a collection whose items are types that serve as keys.

Remarks

Only one object of each type is allowed in the collection because the type is the key and each key must be unique. But you can find objects of different types.

However, you may need to subclass it and extend it to include a convenient TryGetValue<T>(out T value) like so:

public class TypeCollection<V> : KeyedByTypeCollection<V>
{
    public T ValueOrDefault<T>() where T : V
    {
        if (!Contains(typeof(T)))
        {
            return default(T);
        }
        return (T)this[typeof(T)];
    }

    public bool TryGetValue<T>(out T value) where T : V
    {
        if (!Contains(typeof(T)))
        {
            value = default(T);
            return false;
        }

        value = (T)this[typeof(T)];
        return true;
    }
}

This is because the KeyedByTypeCollection<V>.Find<T> method returns the first item in the collection of the specified type T, so in cases where you have a complex polymorphic type hierarchy it may return an instance of a derived type when a base type is present:

var dictionary = new KeyedByTypeCollection<object>();

dictionary.Add("hello");
dictionary.Add(new object());

Assert.IsTrue(dictionary.Find<object>().GetType() == typeof(object)); // FAILS

For further examples of use, see Uses of KeyedByTypeCollection in .Net?.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • does it have a `TryGetValue` method? I think I would have to extend it –  Dec 13 '18 at 09:42
  • well I don't know if this is really the thing I am looking for because find does the following `Returns the first item in the collection of a specified type.` but this collection should only have one item per type? –  Dec 13 '18 at 09:53
  • 1
    @MHComputech - sorry about that, been a while since I used this type. Answer updated. – dbc Dec 13 '18 at 10:09
  • Do you think the `Contains` check is as fast as my custom `TryGetValue` method? Maybe faster? Or less? –  Dec 13 '18 at 10:19
  • and should I extend it or inherit from it ... ? –  Dec 13 '18 at 10:21
  • 1
    @MHComputech - 1) performance: no way to know without testing. The underlying lookup should be via a hash table either way. 2) *extend it or inherit from it* -- we may be using inconsistent terminology, [this answer](https://stackoverflow.com/a/22573918) says they mean the same thing. But i don't think an extension method would be convenient -- I couldn't find a way to get automatic type inferencing to work conveniently. – dbc Dec 13 '18 at 10:25
0

You can reintroduce these methods and make them private:

    private new void Add(Type key, V value)
    {

    }

    private new bool TryGetValue(Type key, out V value)
    {
        value = default(V);
        return false;
    }
Miamy
  • 2,162
  • 3
  • 15
  • 32
0

For your problem there are two ways: inheritance and composition. Inheritance does not allow you to exclude base class methods, so Dictionary is not the best base class for your type.

In composition there is no such problem, because you deside what methods to expose. And this is normal way.

Solution: use composition or find new better base class.

Backs
  • 24,430
  • 5
  • 58
  • 85
0

Implement the IDictionary interface instead:

internal class TypeCollection<V> : IDictionary<Type, V>
{
    protected readonly Dictionary _innerDictionary = new Dictionary<Type,V>();

}

Once you type that much, Visual Studio will underline parts of the code and show an error reminding you that you haven't implemented the interface yet. Right click the error and choose "implement interface through _innerDictionary" and it'll autogenerate everything you need to wire up the methods to _innerDictionary. Then you can modify whatever you want.

John Wu
  • 50,556
  • 8
  • 44
  • 80