Przede wszystkim zadeklarować swój atrybut i dodać go do swoich klas.
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
{
}
Następnie można użyć tego kodu do utworzenia słownika z numerami i typami oraz dynamicznie utworzyć typ.
//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.
Jak można prawdopodobnie zauważyć, nazywając Activator.CreateInstance
nie jest wydajnych. Dlatego jeśli chcesz nieco poprawić wydajność, możesz zmienić słownik na Dictionary<MyEnum,Func<object>>
i zamiast dodawać typy jako wartości, możesz dodać funkcje owijające konstruktora każdej z klas i zwracające je jako obiekty.
EDIT: Dodaję klasę ConstructorFactory
, dostosowaną ze strony this.
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);
}
Można go używać jako alternatywa do Activator.CreateInstance
, zapewnia większą wydajność, jeśli wynik jest buforowane.
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]();
Czy mógłbyś zilustrować problem z, powiedzmy, sytuacją 2 razy 2 (przełączniki w switchach z 2 wartościami wyliczenia każda)? –
Myślę, że nawet jeśli użyjesz atrybutów na podklasach, będziesz musiał użyć przełączniki w przełącznikach w przełącznikach Ponownie będziesz musiał użyć odbicia, aby uzyskać wartości atrybutów dla wszystkich podklas i które będą wolne. – 24x7Programmer