Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use custom-attributes in C# to replace switches in switches?

I have a factory method that returns the correct sub class depending on three enum values. One way to do is, would be to use switches in switches in switches. Obviously, I don't like that option very much.

I thought that another option would be to use attributes in C#. Every sub class would have an attribute with that 3 enum values and in the factory I would only have to get the class that has the same enum values corresponding to the enum values i have in the factory.

However, I am quite new to attributes and I did not find any suitable solution in the web. If anyone, could just give me some hints or some lines of code, I really would appreciate that!

like image 995
user1935239 Avatar asked Jan 03 '13 12:01

user1935239


2 Answers

First of all, declare your attribute and add it to your classes.

enum MyEnum
{
    Undefined,
    Set,
    Reset
}

class MyEnumAttribute : Attribute
{
    public MyEnumAttribute(MyEnum value)
    {
        Value = value;
    }

    public MyEnum Value { get; private set; }
}

[MyEnum(MyEnum.Reset)]
class ResetClass
{
}

[MyEnum(MyEnum.Set)]
class SetClass
{
}

[MyEnum(MyEnum.Undefined)]
class UndefinedClass
{
}

Then, you can use this code to create a dictionary with your enums and types, and dynamically create a type.

//Populate a dictionary with Reflection
var dictionary = Assembly.GetExecutingAssembly().GetTypes().
    Select(t => new {t, Attribute = t.GetCustomAttribute(typeof (MyEnumAttribute))}).
    Where(e => e.Attribute != null).
    ToDictionary(e => (e.Attribute as MyEnumAttribute).Value, e => e.t);
//Assume that you dynamically want an instance of ResetClass
var wanted = MyEnum.Reset;
var instance = Activator.CreateInstance(dictionary[wanted]);
//The biggest downside is that instance will be of type object.
//My solution in this case was making each of those classes implement
//an interface or derive from a base class, so that their signatures
//would remain the same, but their behaviors would differ.

As you can probably notice, calling Activator.CreateInstance is not performant. Therefore, if you want to improve the performance a little bit, you can change the dictionary to Dictionary<MyEnum,Func<object>> and instead of adding types as values you would add functions wrapping the constructor of each of your classes and returning them as objects.

EDIT: I'm adding a ConstructorFactory class, adapted from this page.

static class ConstructorFactory
{
    static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
    {
        var paramsInfo = ctor.GetParameters();
        var param = Expression.Parameter(typeof(object[]), "args");
        var argsExp = new Expression[paramsInfo.Length];
        for (var i = 0; i < paramsInfo.Length; i++)
        {
            Expression index = Expression.Constant(i);
            var paramType = paramsInfo[i].ParameterType;
            Expression paramAccessorExp = Expression.ArrayIndex(param, index);
            Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
            argsExp[i] = paramCastExp;
        }
        var newExp = Expression.New(ctor, argsExp);
        var lambda = Expression.Lambda(typeof(ObjectActivator<T>), newExp, param);
        var compiled = (ObjectActivator<T>)lambda.Compile();
        return compiled;
    }

    public static Func<T> Create<T>(Type destType)
    {
        var ctor = destType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).First();
        Func<ConstructorInfo, object> activatorMethod = GetActivator<Type>;
        var method = typeof(ConstructorFactory).GetMethod(activatorMethod.Method.Name, BindingFlags.Static | BindingFlags.NonPublic);
        var generic = method.MakeGenericMethod(destType);
        dynamic activator = generic.Invoke(null, new object[] { ctor });
        return () => activator();
    }

    delegate T ObjectActivator<out T>(params object[] args);
}

You can use it as an alternative to Activator.CreateInstance, it provides greater performance if the result is cached.

var dictionary = Assembly.GetExecutingAssembly().GetTypes().
    Select(t => new { t, Attribute = t.GetCustomAttribute(typeof(MyEnumAttribute)) }).
    Where(e => e.Attribute != null).
    ToDictionary(e => (e.Attribute as MyEnumAttribute).Value, 
                 e => ConstructorFactory.Create<object>(e.t));
var wanted = MyEnum.Reset;
var instance = dictionary[wanted]();
like image 143
Mir Avatar answered Sep 23 '22 15:09

Mir


Have a look at this article: Creating Custom Attributes. You can then use reflection (for instance GetCustomAttributes) to get the attributes and their values.

Hope this helps

like image 29
rickvdbosch Avatar answered Sep 26 '22 15:09

rickvdbosch