开发者

C# -- injecting an IDictionary<A, IList<B>>

I'd like to inject an instance an IDictionary<A, IList<B>> into a class, but want to keep control over the implementation of IList<B> used. The way I have of doing it -- with generics -- feels clunky. It makes the class definition unwieldy, and would only get worse if there were multiple similar objects I wanted to inject.

public class Lookup<T1, T2, T3> where T2 : IList<T3>, new()
{
    private IDictionary<T1, T2> _underlyingDict;

    public Lookup(IDictionary<T1, T2> dict)
    {
        _underlyingDict = dict;
    }

    public T2 Get(T1 key)
    {
        return _underlyingDict[key];
    }

    public void Add(T1 key, T3 value)
    {
        if (!_underlyingDict.ContainsKey(key))
        {
            _underlyingDict[key] = new T2();
        }
        _underlyingDict[key].Add(val开发者_StackOverflowue);
    }
}

Are there any problems with this approach, and is there a cleaner solution?


While it is possible to do what you're doing you may want to consider this:

If you are doing dependency-injection you typically don't inject primitive types. This includes basic collections. Dependency-injection works best when injecting long-lived singleton-scoped objects, akin to services in DDD-speak.

You may want to consider making a type to encompass the idea that this IDictionary<int, IList<B>> is trying to represent. What is this object used for? How does it interact with other objects in your system? The answers to those questions may lead for a complex type, perhaps with some functionality mixed in. Maybe its a FooRepository and you only need certain methods of the full IDictionary object exposed so you end up with something like this:

public class FooRepository<T>
{
    private IDictionary<int, IList<T>> m_repository;

    FooRepository()
    {
        m_repository = new Dictionary<int, List<T>>();
    }

    public Add(int key, IList<T> value)
    {
        m_repository.Add(key, value);
    }

    // other public methods to expose
}

The reason you might consider doing this is that anything using the IDictionary<int, List<T>> probably has access to way more methods than it needs and you want to follow good data encapsulation practices to allow consumers of this object to access only those things that they require.


That looks okay to me, if you're happy for the caller of the constructor to still have a reference to the dictionary that your collection is based on. I would personally change the Add method to use fewer dictionary lookups though:

public void Add(T1 key, T3 value)
{
    T2 list;
    if (!_underlyingDict.TryGetValue(key, out list))
    {
        list = new T2();
        _underlyingDict[key] = list;
    }
    list.Add(value);
}

You might also want to consider renaming the type from Lookup given that that clashes with System.Linq.Lookup. Obviously it's possible that you're creating your own replacement Lookup class, which would explain this... but if there's a chance that clients will be using both your class and "normal" LINQ, I'd consider a rename.


Another option, which removes the need for the extra type parameter, is to accept a delegate to create a new instance of the relevant list type:

public class Lookup<T1, T2>
{
    private IDictionary<T1, IList<T2>> _underlyingDict;
    private Func<IList<T2>> _listCreator;

    public Lookup(IDictionary<T1, IList<T2>> dict,
                  Func<IList<T2>> listCreator)
    {
        _underlyingDict = dict;
        _listCreator = listCreator;
    }

    ...
}

then where you previously had new T2() just call _listCreator().


I prefer to create specific classes inherited from generic base classes:

public class FooLookup : Lookup<int, List<Foo>, Foo>
{
    public FooLookup(IDictionary<int, List<Foo>> dict)
        : base(dict)
    {            
    }
}

It makes code much cleaner. Compare:

var dictionary = new Dictionary<int, List<Foo>>();

FooLookup fooLookup = new FooLookup(dictionary);
Lookup<int, List<Foo>, Foo> lookup = new Lookup<int, List<Foo>, Foo>(dictionary);

Main reason why I don't like to use classes with T1, T2, T3, T4 in code is human mind - average people can keep about 7 (+/- 2) things in their head at a time. So, creating T1, T2, T3, T4 will take 4 of that things, leaving 3 to business logic.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜