views:

191

answers:

7

Greetings!

I have a class which is used like a cache:

public sealed class MyCache<T> : IDisposable
{
    private ReaderWriterLockSlim theLock = new ReaderWriterLockSlim();
    private Dictionary<int, T> theCache = new Dictionary<int, T>();

    public void Add(int key, T value)
    {
        // ... logic/code to add to the dictionary
    }

    public void Clear()
    {
        theLock.EnterWriteLock();
        try
        {
            theCache.Clear();
        }
        finally
        {
            theLock.ExitWriteLock();
        }
    }
}

This cache is used many times, so there are often multiple instances of this at any given time.

Example 1:

public static class SpecialPageCache
{
    public static MyCache<string> SpecialPage = new MyCache<string>();
}

Example 2:

public static class DdListCache
{
    public static MyCache<List<int, string>> DdlList = new MyCache<List<int, string>>();
}

And so on.

I have a service that can clear the caches on-demand, but unfortunately, each one has to be cleared like so:

private void ClearThemAll()
{
    SpecialPageCache.SpecialPage.Clear();
    DdListCache.DdlList.Clear();
    // repeat for all other caches that may exist ...
}

How can I use reflection (or something else?) to call each cache's Clear() method without having to explcitly do it for each one like I do in the above ClearThemAll() method?

+1  A: 

You could store references to all of your instanced caches in a list. Then iterate same list, and call Clear on each MyCache. =)

J. Steen
+3  A: 

Ick. You'd have to go through all the types in the assembly that you're interested in, and check all the static fields. This is made even more interesting because it's a generic type. Your life will be simpler if you have a nongeneric base class:

public abstract class MyCache : IDisposable
{
    public abstract void Clear();
}

public sealed class MyCache<T> : MyCache
{
    // ...
}

Then at least it's relatively easy to detect whether the type of a particular field is a MyCache, fetch its value and call Clear on it without messing around with reflection over generic types.

This is generally a nasty problem though - are you sure you want to clear all the caches like this, without really "understanding" which caches you're clearing?

Jon Skeet
That kind of leads into a second question where in some situations, certain cache's may need to be cleared while others don't have to be.
Bullines
So have a ClearableCache and NonclearableCache, or pass an argument to Clear() that allows it to make the decision.
Mark
Though following on from Jon's final remark, you might want the caches to (optionally) respond to events, rather than force the cache clear.
Mark
Downvoting because I think storing references to all the caches is a better solution than reflection
Jim Arnold
Jim: the "service that can clear the caches on-demand" presumably already has this list, surely?
Mark
Jim: Thanks for explaining the downvote. I hope I made it fairly clear in the answer that I didn't really like the use of reflection here. I agree that if it's feasible, a list of references to caches to clear would be nicer.
Jon Skeet
@Mark: with the OP's current design each new cache requires an explicit, hard-coded reference, which is probably what led him to consider reflection. I'm just pointing out that creating and storing the references at run time instead of compile time is a better solution.
Jim Arnold
A: 

Reflection sounds nasty. Without knowing more about your object lifetime, would the following work?

public abstract class MyCacheBase : IDisposable {
    public static List<MyCache> caches = new List<MyCache>();

    public MyCacheBase() {
        caches.Add(this); // Add all constructed caches to the list
    }

    public static void ClearAllCaches() {
        foreach (MyCache cache in cache) // clear all constructed
            cache.Clear();               // caches in the list.
    }

    public void Finalize() {
        Dispose();
    }

    public void Dispose() {
        caches.Remove(this);  // Remove disposed classes from the list
    }

    public abstract void Clear();
}

public sealed class MyCache<T> : MyCacheBase
{
    // Rest of the implementation
}

(Thanks to Jon for noting the genericity. Almost missed it.)

If you want to have something like user specific caches you could add a user specific CacheFactory which would keep track of the caches created through it and its ClearAll() method would only clear those caches.

Mikko Rantanen
+1  A: 

Why do you need to specifcally clear them are they using resources which need to be released ?

I'm wondering if you couldnt use the System.WeakReference so that the cache is garbarge collected as and when ?

http://msdn.microsoft.com/en-us/library/system.weakreference.aspx

Saint Gerbil
+2  A: 
public interface ICache : IDisposable
{
    void Clear();
}

public interface ICache<T> : ICache
{
}

public abstract class CacheBase<T> : ICache<T>
{

}

public sealed class SpecialPageCache : CacheBase<string>
{
    internal SpecialPageCache()
    {
    }

}

public static class CacheFactory
{
    private static List<ICache> cacheList = new List<ICache>();

    public static TCache Create<TCache>()
        where TCache : ICache, new()
    {
        var result = new TCache();
        cacheList.Add(result);
        return result;
    }

    public static void ClearAll()
    {
        cacheList.ForEach((c) => c.Clear());
    }
}
A: 

This sound really familiar to how Inversion of Control containers work. Why not just have something like a

Dictionary<Type, Dictionary<int, object>>

All of the functions would then take a type T and then use that type to look up the appopriate dictionary. One static MyCache could handle all your type and it could be disposed with one call.

Jacob Adams
A: 

If you really want to do reflection then you'd do something like this:

List<object> caches;
foreach (object obj in caches)
{
    Type t = obj.GetType();
    MethodInfo m = t.GetMethod("Clear");

    // Object does not have a public instance method named "Clear"
    if (m == null) { continue; }

    m.Invoke(obj, new object[0]);
}
Colin Burnett