tags:

views:

340

answers:

2

I have an array which stores a dictionary of Types:

//The dictionary:
Dictionary<CacheKey,Type> TypeLookup;

//This is the enum:
public enum CacheKey
{
    UserProfile,
    CustomerQuickSearch,
    CommissionConfiguration
}

I would like to use this Dictionary to declare a variable of type T

        //instead of 
        T myvar;

        //I want to dynamically declare myvar as:
        //1)get the type for the cacheKey from the dictionary:
        Type type = TypeLookup[cacheKey];
        //2)declare myvar as the corresponding Type:
        type myvar;

The background is that I am building a Distributed Caching infrastructure. I have a great little CachingProvider that allows you to update an item in the cache.

I would like to expose this method as a webservice so that all the servers in my farm can have their cache updated. But I would like to have only one method exposed as a webservice which then updates the corresponding item in cache.

This is the method I'm trying to expose:

   public static void UpdateCacheEntryItem<T>(CacheKey cacheKey, int id)
    {
        //look up the cacheEntry in cache which is a dictionary.
        Dictionary<int, T> cacheEntry = (Dictionary<int, T>) CacheRef[cacheKey.ToString()];

        //call the corresponding method which knows how to hydrate that item and pass in the id.
        cacheEntry[id] = (T)HydrateCacheEntryItemMethods[cacheKey].Invoke(id);
    }

Things I've tried: 1) I tried exposing the method directly as a WCF service but of course that doesn't work because of the on the method. 2) I tried casting the Dictionary which would be find because I don't need to do anthing with the return value, I just need to update the item in cache. But that didn't work either. Error that I get: Unable to cast object of type 'System.Collections.Generic.Dictionary2[System.Int32,CachingPrototype.CustomerQuickSearch]' to type 'System.Collections.Generic.Dictionary2[System.Int32,System.Object]'.

Your comments were very helpful and helped me to answer my question. The solution I came up with is to simply wrap my WCF service method in a switch statement so that I could call the UpdateCacheEntryItem method with the correct type of T. Since there is no way to convert from Type to the generic T operator, this is the only option. Since I don't have that many types in Cache, this works pretty well. (The other solution would be to use an interface as stated below but that would not be as strongly typed as I would like.)

    [OperationContract]
    public void UpdateCacheEntryItem(CacheKey cacheKey, int id)
    {
        switch (cacheKey)
        {
            case CacheKey.UserProfile:
                CacheProvider.UpdateCacheEntryItem<UserProfile>(cacheKey, id);
                break;
            case CacheKey.CommissionConfig:
                CacheProvider.UpdateCacheEntryItem<CommissionConfig>(cacheKey, id);
                break;
            case CacheKey.CustomerQuickSearch:
                CacheProvider.UpdateCacheEntryItem<CustomerQuickSearch>(cacheKey, id);
                break;
            default:
                throw new Exception("Invalid CacheKey");
        }

Thanks everyone for your help, you are brilliant!

+10  A: 

The idea of "dynamically declaring a variable" is contrary to the whole point of there being a type as part of the declaration of a variable. The idea is that you can tell the compiler the type, so that it can check what you're doing. In this case you haven't expressed any information about the type at all. You might as well just declare myVar as being of type object; that's basically the same as saying "I know pretty much nothing about the value of myVar, except that it's a reference."

If you've got a common interface of course, that would be great - and then you could use the members of that interface safely (after creating/fetching an appropriate instance, of course). But otherwise, there's really not a lot you can do unless you know something about the type at compile time.

In C# 4 you could declare the variable to be of type dynamic which would make all the binding dynamic - basically you can do pretty much what you like with it, and it will all be resolved at execution time. I'd advise using static typing wherever you can though, so that errors can be caught at compile time instead.

Jon Skeet
+1 -- but I suspect that there is a way to solve the actual problem if only we knew what it really was.
tvanfosson
@tvanfosson: Possibly... possibly not. There are certainly cases where generics just don't let you express what you need. For example, you can't have a dictionary from `Type` to "an instance of the key type". You can build one and rely on its own integrity, but you can't express it directly with generics.
Jon Skeet
@Jon Skeet - I'm thinking that it may be a case of "I get the name of the thing I want from a web page (column name, etc.), now I want to create or lookup one of these," which is solvable. Perhaps, I'm just being influenced by my own typical context, though. I agree with the idea of using an interface if possible.
tvanfosson
Thanks for the answer and comments Jon Skeet!
BrokeMyLegBiking
+2  A: 

It looks to me that an interface and some casting will solve your problem. Just have each of your cacheable classes implement an interface. Store items of this type in your dictionary. Presumably, CacheRef would be of type Dictionary<CacheKey,Dictionary<CacheKey,ICacheable>>. All that is left is to make sure that your cacheable classes implement the interface.

public interface ICacheable
{
}

public static void UpdateCacheEntryItem(CacheKey cacheKey, int id)
{
    //look up the cacheEntry in cache which is a dictionary.
    Dictionary<CacheKey,ICacheable> cacheEntry = CacheRef[cacheKey.ToString()];

    //call the corresponding method which knows how to hydrate that item and pass in the id.
    cacheEntry[id] = (ICacheable)HydrateCacheEntryItemMethods[cacheKey].Invoke(id);
}

Note that this isn't, like @Jon Skeet says in his comments to his answer, enforcing the type in your Dictionary. It's up to your code to make sure that you are putting the right kind of objects into each cache. I'd be comfortable with this as long as your hydration methods were covered by unit tests to ensure that when given a particular key, they always produce objects of the appropriate type.

tvanfosson
this is a good solution, I tried this and it works. but I decided to use a switch statement in my WCF call so that I could continue to use strong typing.
BrokeMyLegBiking