Best way to dynamically create classes instead of using a switch block
Asked Answered
R

6

6

Currently I have my VaryByCustom functionality implemented in classes that implement an interface IOutputCacheVaryByCustom

public interface IOutputCacheVaryByCustom
{
    string CacheKey { get; }
    HttpContext Context { get; }
}

A class implementing this interface has a few conventions the name of the class will be "OutputCacheVaryBy_______" where the blank is the value that is passed in from the varyByCustom property on pages. The other convention is that Context will be set through constructor injection.

Currently I'm basing this off an enum and a switch statement similar to

public override string GetVaryByCustomString(HttpContext context, 
                                              string varyByCustomTypeArg)
{
    //for a POST request (postback) force to return back a non cached output
    if (context.Request.RequestType.Equals("POST"))
    {
        return "post" + DateTime.Now.Ticks;
    }
    var varyByCustomType = EnumerationParser.Parse<VaryByCustomType?>
                            (varyByCustomTypeArg).GetValueOrDefault();


    IOutputCacheVaryByCustom varyByCustom;
    switch (varyByCustomType)
    {
        case VaryByCustomType.IsAuthenticated:
            varyByCustom = new OutputCacheVaryByIsAuthenticated(context);
            break;
        case VaryByCustomType.Roles:
            varyByCustom = new OutputCacheVaryByRoles(context);
            break;
        default:
            throw new ArgumentOutOfRangeException("varyByCustomTypeArg");
    }

    return context.Request.Url.Scheme + varyByCustom.CacheKey;
}

Since I always know that the class will be OutputCacheVaryBy + varyByCustomTypeArg and the only constructor argument will be context I realized I could bypass needing this glorified if else block and could just instantiate my own object with Activator.

With this being said, reflection is not my strong suit and I know that Activator is substantially slow comparatively to static creation and other ways to generate objects. Is there any reason why I should stick with this current code or should I use Activator or a similar way to create my object?

I've seen the blog http://www.smelser.net/blog/post/2010/03/05/When-Activator-is-just-to-slow.aspx but I'm not really sure how this would apply since I'm working with types at runtime not static T.

Ritzy answered 26/8, 2010 at 22:22 Comment(3)
Are the objects expensive (time consuming) to create? Is the context required during construction, can it set via the Context property instead? The answer to these questions is required to provide the best solution.Guanine
No the objects trivial to create, and the context would optimally be set in the constructor because that's a core dependency to the class however it could be exposed and set on the property but that leaves room for NREs.Ritzy
what version of c# are you using?Vibes
V
5

You don't really need to use reflection since it's a rather limited set of possible values. You could however do something like this

internal class Factory<T,Arg>
{
   Dictionary<string,Func<Arg.T>> _creators;
   public Factory(IDictionary<string,Func<Arg,T>> creators)
  {
     _creators = creators;
  }
}

and substitute your creation logic with

_factory[varyByCustomTypeArg](context);

it's not as fast as a switch but it keeps construction and use nicely seperate

Vibes answered 10/9, 2010 at 23:5 Comment(0)
L
7

If Reflection is too slow for you. You can probably get your own ObjectFactory working. It's really easy. Just add a new method to your interface.

    public interface IOutputCacheVaryByCustom
    {
        string CacheKey { get; }
        IOutputCacheVaryByCustom NewObject();
    }

Than create a static readonly CloneDictionary that holds the object templates.

    static readonly
        Dictionary<VaryByCustomType, IOutputCacheVaryByCustom> cloneDictionary
        = new Dictionary<VaryByCustomType, IOutputCacheVaryByCustom>
        {
            {VaryByCustomType.IsAuthenticated, new OutputCacheVaryByIsAuthenticated{}},
            {VaryByCustomType.Roles, new OutputCacheVaryByRoles{}},
        };

If you finished with that, you can use the enum that you already have in order to select the template in the dictionary and call NewObject()

        IOutputCacheVaryByCustom result = 
             cloneDictionary[VaryByCustomType.IsAuthenticated].NewObject();

Is just that simple. The NewObject() method you have to implement, will return a new Instance by directly creating the object.

    public class OutputCacheVaryByIsAuthenticated: IOutputCacheVaryByCustom
    {
        public IOutputCacheVaryByCustom NewObject() 
        {
            return new OutputCacheVaryByIsAuthenticated(); 
        }
    }

That's all you need to have. And it's incredible fast.

Lepine answered 10/9, 2010 at 5:23 Comment(0)
V
5

You don't really need to use reflection since it's a rather limited set of possible values. You could however do something like this

internal class Factory<T,Arg>
{
   Dictionary<string,Func<Arg.T>> _creators;
   public Factory(IDictionary<string,Func<Arg,T>> creators)
  {
     _creators = creators;
  }
}

and substitute your creation logic with

_factory[varyByCustomTypeArg](context);

it's not as fast as a switch but it keeps construction and use nicely seperate

Vibes answered 10/9, 2010 at 23:5 Comment(0)
L
3

I really like to have object creation taken care of by someone else. For example if i need different concrete implementations of an interface an IoC container has worked wonders for me.

As a simple example using unity you have a configuration portion linking up keys to implementations like so:

public void Register(IUnityContainer container)
{
   container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByIsAuthenticated>("auth");
   container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByRoles>("roles");
}

and your creation would look much simpler like so:

//injected in some form
private readonly IUnityContainer _container;

public override string GetVaryByCustomString(HttpContext context, 
                                              string varyByCustomTypeArg)
{
    //for a POST request (postback) force to return back a non cached output
    if (context.Request.RequestType.Equals("POST"))
    {
        return "post" + DateTime.Now.Ticks;
    }
    try
    {
    IOutputCacheVaryByCustom varyByCustom = _container.Resolve<IOutputCacheVaryByCustom>(varyByCustomTypeArg, new DependencyOverride<HttpContext>(context));
    }
    catch(Exception exc)
    {
       throw new ArgumentOutOfRangeException("varyByCustomTypeArg", exc);
    }
    return context.Request.Url.Scheme + varyByCustom.CacheKey;
}

Or if IoC is not an option i would let a factory create the concrete classes, so you never have to worry your actual methods about them.

Laurentium answered 10/9, 2010 at 0:0 Comment(0)
P
1

Stay with the switch statement. If you only have a couple of possible cases like this, then you are just trying to use clever abstraction to avoid sitting down and getting the hard parts of your program working...

That said, from your question it seems using the Activator might work for you. Have you tested it? Was it really too slow?

Alternatively, you could just keep a bunch of factory methods in a Dictionary<string, Func<IOutputCacheVaryByCustom>. This I would use, if you are creating these objects often (in a loop). You could then also optimize the string key to your enum and be done with the conversion. Going more abstract will just hide the intent of this piece of code...

Preselector answered 14/9, 2010 at 12:41 Comment(0)
L
0

Here is an example to create new object

public static object OBJRet(Type vClasseType)
{
    return typeof(cFunctions).GetMethod("ObjectReturner2").MakeGenericMethod(vClasseType).Invoke(null, new object[] { });
}

public static object ObjectReturner2<T>() where T : new()
{
    return new T();
}

Some infos:

  • cFunctions is the name of my static class containing the functions

Also an example where I get the class contained into an arraylist:

    public static object OBJRet(Type vClasseType, ArrayList tArray, int vIndex)
    {
        return typeof(cFunctions).GetMethod("ObjectReturner").MakeGenericMethod(vClasseType).Invoke(null, new object[] { tArray, vIndex });
    }

    public static object ObjectReturner<T>(ArrayList tArray, int vIndex) where T : new()
    {
        return tArray[vIndex];
    }
Laplace answered 27/8, 2010 at 17:43 Comment(1)
This is what I used to be able to get different class contained into an arraylist that needed similar transformation. These are static class/methods, but they could be normal too, they were static only because it was a generic class used as a dll. I am sure you will figure out how to adapt it for your needs.Laplace
W
0

Use reflection.

    public override string GetVaryByCustomString(HttpContext context,   
                                          string varyByCustomTypeArg)
    {
        //for a POST request (postback) force to return back a non cached output   
        if (context.Request.RequestType.Equals("POST"))   
        {   
            return "post" + DateTime.Now.Ticks;   
        }

        Type type = Type.GetType("OutputCacheVaryBy" + varyByCustomTypeArg, false)
        if (type == null)
        {
            Console.WriteLine("Failed to find a cache of type " + varyByCustomTypeArg);
            return null;
        }

        var cache = (IOutputCacheVaryByCustom)Activator.CreateInstance(type, new object[]{context});
        return context.Request.Url.Scheme + cache.CacheKey;
    } 

You might have to prefix typename with a namespace: "My.Name.Space.OutputCacheVaryBy". If that doesnt work, try with a assembly qualified name:

Type.GetType("Name.Space.OutputCacheVaryBy" + varyByCustomTypeArg + ", AssemblyName", false)
Wile answered 14/9, 2010 at 12:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.