2013-01-03 10 views
5

Mam metodę fabryki, która zwraca poprawną podkategorię w zależności od trzech wartości wyliczeniowych. Jednym ze sposobów jest użycie przełączników w przełącznikach. Oczywiście, bardzo nie lubię tej opcji.Jak używać niestandardowych atrybutów w języku C# do zastępowania przełączników w przełącznikach?

Myślałem, że inną opcją byłoby użycie atrybutów w języku C#. Każda klasa podrzędna miałaby atrybut z tymi trzema wartościami wyliczeniowymi, aw fabryce musiałbym tylko uzyskać klasę, która ma takie same wartości wyliczeniowe odpowiadające wartościom wyliczonym, które mam w fabryce.

Jednak jestem zupełnie nowy w kwestii atrybutów i nie znalazłem żadnego odpowiedniego rozwiązania w sieci. Jeśli ktokolwiek mógłby mi podać jakieś wskazówki lub kodeks, naprawdę to doceniam!

+0

Czy mógłbyś zilustrować problem z, powiedzmy, sytuacją 2 razy 2 (przełączniki w switchach z 2 wartościami wyliczenia każda)? –

+3

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

Odpowiedz

3

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](); 
+0

Uwielbiam pomysł umieszczania typów w słowniku, aby uzyskać dostęp za pomocą klucza. Świetna odpowiedź. –

1
[AttributeUsage(AttributeTargets.Class)] 
    public class SampleClass : Attribute { 
     public SampleClass() : base() { } 
     public SampleClass(YourEnum attributeValue) : this() { MyAttributeProperty = attributeValue; } 
     public YourEnum MyAttributeProperty { get; set; } 
    } 

    public enum YourEnum { Value1, Value2, Value3 } 

    [SampleClass(YourEnum.Value1)] 
    public class ExampleValue1Class { } 

    public class LoadOnlyClassesWithEnumValue1 { 
     public LoadOnlyClassesWithEnumValue1() { 

      Type[] allTypes = Assembly.GetExecutingAssembly().GetExportedTypes(); 
      foreach (var type in allTypes) { 
       if (type.GetCustomAttributes(typeof(SampleClass), false).Length > 0) { 
        SampleClass theAttribute = type.GetCustomAttributes(typeof(SampleClass), false).Single() as SampleClass; 
        // this type is using SampleClass - I use .Single() cause I don't expect multiple SampleClass attributes, change ths if you want 
        // specify true instead of false to get base class attributes as well - i.e. ExampleValue1Class inherits from something else which has a SampleClass attribute 
        switch (theAttribute.MyAttributeProperty) { 
         case YourEnum.Value1: 
          // Do whatever 
          break; 
         case YourEnum.Value2: 
          // you want 
          break; 
         case YourEnum.Value3: 
         default: 
          // in your switch here 
          // You'll find the ExampleValue1Class object should hit the Value1 switch 
          break; 
        } 
       } 
      } 
     } 
    } 

W ten sposób można określić enum jako parametr do atrybutu. W istocie jest to bardzo prosty i lekki pojemnik DI. Proponuję coś bardziej złożonego, użyj czegoś takiego jak StructureMap lub NInject.

0

Możliwe jest użycie atrybutów do przechowywania informacji, ale ostatecznie proces decyzyjny będzie nadal wymagał i prawdopodobnie nie będzie się różnił; tylko z dodatkową złożonością atrybutów. Charakter decyzji pozostaje niezmieniony niezależnie od tego, skąd pochodzą informacje, aby podjąć decyzję, z istniejących trzech wyliczeń lub atrybutów.

Może okazać się bardziej owocne poszukiwanie sposobu połączenia trzech wyliczeń.

Wydzielanie może być dowolnym integralnym typem, więc najprostszym sposobem na wyeliminowanie zagnieżdżonych (i nadmiarowych) przełączników jest połączenie wyliczeń razem. Jest to najłatwiejsze, jeśli wyliczenie jest zbiorem wartości flag. Oznacza to, że każda wartość wyliczenia ma wartość pojedynczego bitu w ciągu binarnym (1 dla pierwszego bitu, 2 dla drugiego bitu, 4 dla trzeciego, 8, 16 i tak dalej).

Pod warunkiem, że wartości każdego z wyliczeń mogą być połączone, redukuje proces selekcji do pojedynczej instrukcji przełączania. Najlepiej można to zrobić, łącząc, pomnażając lub dodając wartości wyliczeniowe - ale w jaki sposób są one ze sobą połączone, zależy to od wyliczeń i nie znając szczegółów, trudno jest podać bardziej konkretny kierunek.

1

Innym rozwiązaniem byłoby użycie kontenera Dependency Injection (DI). Na przykład przy użyciu Unity DI można:

// Register a named type mapping 
myContainer.RegisterType<IMyObject, MyRealObject1>(MyEnum.Value1.ToString()); 
myContainer.RegisterType<IMyObject, MyRealObject2>(MyEnum.Value2.ToString()); 
myContainer.RegisterType<IMyObject, MyRealObject3>(MyEnum.Value3.ToString()); 
// Following code will return a new instance of MyRealObject1 
var mySubclass = myContainer.Resolve<IMyObject>(myEnum.Value1.ToString()); 

przykładów wykorzystania Unity: Implementing the Microsoft Unity (Dependency Injection) Design Pattern

Oczywiście można użyć dowolnego kontenera DI (Zamek Windsor, StructureMap, Ninject Oto lista niektórych z dostępnych. Pojemniki .NET DI List of .NET Dependency Injection Containers (IOC)

Powiązane problemy