1

I'm talking about c# 3.5 3.0. I know how to do it when cache or ServiceProvider can have only one instance for the whole application. In this case ServiceProvider can look like this

public static class Service<T>
{
    public static T Value {get; set;}
}

and can be used for different types like this:

Service<IDbConnection>.Value = new SqlConnection("...");
Service<IDesigner>.Value = ...;
//...
IDbCommand cmd = Service<IDbConnection>.Value.CreateCommand();

Static cache is also easy:

public static class Cache<T>
{
    private static Dictionary<int, T> cache = new Dictionary<int, T>();

    public static void Add(int key, T value)
    {
        cache.Add(key, value);
    }

    public static T Find(int key)
    {
        return cache[key];
    }
}

and can be used like this:

Cache<string>.Add(1, "test");
Cache<DateTime>.Add(2, DateTime.Now);
//...
string found = Cache<string>.Find(1);


My question is: how can I create similiar cache or service provider when I want to have 2 or more different instances of each. Here is example code, how I want to use service provider:

ServiceProvider provider = new ServiceProvider();
provider.Add<IDbConnection>(new SqlConnection("..."));
provider.Add<IDesigner>(...);
//...
ServiceProvider provider1 = new ServiceProvider();
provider1.Add<IDbConnection>(new SqlConnection("..."));
//...
//...
IDbCommand cmd1 = provider.GetService<IDbConnection>().CreateCommand();
IDbCommand cmd2 = provider1.GetService<IDbConnection>().CreateCommand();

The only implementation that I have in my head is using casting which I want to avoid.

public class ServiceProvider
{
    private Dictionary<Type, object> services = new Dictionary<Type, object>();
    public void Add<T>(T value)
    {
        services.Add(typeof(T), value);
    }

    public T GetService<T>()
    {
        return (T) services[typeof (T)];
    }
}
SeeR
  • 2,158
  • 1
  • 20
  • 35

4 Answers4

2

Why are you particularly desperate to avoid casting? Yes, it feels "unsafe" - but you can basically guarantee that it's not going to be an issue in ServiceProvider, and the clients aren't doing any casting.

This is a reasonably common problem, but I don't believe there's any nice solution to it within .NET generics - it's a type relationship which can't be expressed, basically.

EDIT: I've now blogged about this and encapsulated the behaviour in a type. Feel free to take that code if it keeps things cleaner for you.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thinking about it, this is a really common issue. I think it's worth implementing a type which encapsulates exactly this part (the dictionary add/lookup) so that the casting is really tightly restricted to that single class. Will experiment tonight and write a blog entry... – Jon Skeet Oct 07 '08 at 13:07
  • From tests that I found on the internet it seems that generic version of code is usually 4-5 times faster than the one with cast. I think it's a good reason to investigate this and cache is a good example of code that will benefit from this. – SeeR Oct 07 '08 at 13:43
  • How much of the time do you expect to be pulling stuff out of the cache? Do you really think it will be a performance bottleneck? If you're doing *anything* even slightly networky, I'd expect any difference due to a cast to disappear into the noise. – Jon Skeet Oct 07 '08 at 15:03
  • One guy in our company wrote application that downloads something like 100 000 records from database at start to perform some calculations befpre showing anything to user. This is good example when cache class will benefit from this. But my question is: is it possible? not is it usable :-) – SeeR Oct 07 '08 at 16:30
  • How long do you think 100,000 casts will take, really? I did a quick benchmark on my Eee PC (i.e. hardly server class hardware!) and it was able to perform 100 million casts in about 1.5 seconds. But back to theory, no, I don't believe you can get away without the cast. – Jon Skeet Oct 07 '08 at 16:37
1

As I posted to Jon Skeet's blog, the following approach might help you avoid casts, if that's a worry (though perhaps this introduces some other more serious issues than casting :)).

If you have a weak dictionary implementation (one that uses weak-reference keys and cleans out otherwise unreferenced keys and their associated values), you could try something like this:

   public class TypeDictionary  
   {    
       private class InnerTypeDictionary<T>
       {
           static WeakDictionary<TypeDictionary, T> _innerDictionary = new WeakDictionary<TypeDictionary, T>();
           public static void Add(TypeDictionary dic, T value)
           {
               _innerDictionary.Add(dic, value);
           }

           public static T GetValue(TypeDictionary dic)
           {
               return _innerDictionary[dic];
           }
       }

       public void Add<T>(T value)
       {
           InnerTypeDictionary<T>.Add(this, value);
       }

       public T GetValue<T>()
       {
           return InnerTypeDictionary<T>.GetValue(this);
       }
   }

It has the benefit of making all the type lookups into static generic type lookups, without direct recourse to System.Type objects, so I guess that might give you a performance kick. I would be interested to know if it does suit your caching scenario.

Brian Deragon
  • 2,929
  • 24
  • 44
James Hart
  • 1,251
  • 1
  • 9
  • 11
0

I think I found the solution. Here you have my implementation of ServiceProvider You can find the description of it on my blog.

public class ServiceContainer : IDisposable
{
    readonly IList<IService> services = new List<IService>();

    public void Add<T>(T service)
    {
        Add<T,T>(service);
    }

    public void Add<Key, T>(T service) where T : Key
    {
         services.Add(new Service<Key>(this, service));
    }

    public void Dispose()
    {
        foreach(var service in services)
            service.Remove(this);
    }

    ~ServiceContainer()
    {
        Dispose();
    }

    public T Get<T>()
    {
        return Service<T>.Get(this);
    }
}

public interface IService
{
    void Remove(object parent);
}

public class Service<T> : IService
{
    static readonly Dictionary<object, T> services = new Dictionary<object, T>();

    public Service(object parent, T service)
    {
        services.Add(parent, service);
    }

    public void Remove(object parent)
    {
        services.Remove(parent);
    }

    public static T Get(object parent)
    {
        return services[parent];
    }
}

Yes it uses static field, but all references are removed in finalizer so the only drawback is that ServiceProvider stays one GC generation longer than usually.

EDIT: OK, after few tries I must admit that Jon Skeet was right, currently there is no simple solution to this problem. My solution written above can work only if I fulfill 2 constraints:

  1. I use Dictionary<WeakReference, T> services instead of Dictionary<object, T> services
  2. No service will have reference to ServiceProvider.

Otherwise you will have memory leaks :-(

Simple solution that Microsoft could provide is to create native WeakReference< T > which will solve constraint No 2. and we can write services like this:

Dictionary<WeakReference, WeakReference<T>> services
SeeR
  • 2,158
  • 1
  • 20
  • 35
  • I don't see why the methods in Service are instance methods at all. I'd be very wary of the GC implications of this - Service itself really relies on everything being removed, or it'll hang around forever. That may be suitable for your application, but I'd be happy with casting personally. – Jon Skeet Oct 09 '08 at 22:18
  • They are instance because ServiceProvider Need to know what types to remove. According to safety Making Service.services public or protected and using GC.Collect() One can easily unit test if this solution behaves correctly and don't have memory leaks. – SeeR Oct 09 '08 at 22:57
  • Oh, and I don't have any concrete application in mind - it was rather academic curiosity if it's possible to write application without cast. Maybe I'll use it in some project in the future :-) – SeeR Oct 09 '08 at 23:00
0

There is no good way to do this without casting. Don't get hung up on the casting cost. Focus on things that actually impact performance...for example, hashing isn't free to begin with. You shouldn't be calling back into your service provider every time you want to use a service. Get the reference a single time and you don't have to worry about the mounting costs of retrieval:

var service = provider.GetService<MyService>();
service.DoSomething();
service.DoSomethingElse();
Brian Deragon
  • 2,929
  • 24
  • 44
jezell
  • 2,532
  • 14
  • 12