2013-01-03 70 views
5

我有一個工廠方法,根據三個枚舉值返回正確的子類。要做的一種方法是,在交換機中使用交換機。很明顯,我不太喜歡這個選項。如何在C#中使用自定義屬性替換開關中的開關?

我認爲另一種選擇是在C#中使用屬性。每個子類都有一個具有3個枚舉值的屬性,並且在工廠中,我只需要獲得與工廠中的枚舉值相對應的枚舉值相同的類。

但是,我對屬性還是比較陌生的,在網絡中我沒有找到任何合適的解決方案。如果有人,可以給我一些提示或代碼行,我真的很感激!

+0

你能舉例說明一個2倍2的情況(每個開關有2個枚舉值的開關)? –

+3

我認爲即使你在子類上使用屬性,你也必須使用切換開關中的開關重新考慮你將不得不使用反射來獲取所有子類的屬性值,哪些會很慢。 – 24x7Programmer

回答

3

首先,聲明你的屬性,並把它添加到您的類。

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 
{ 
} 

然後,您可以使用此代碼創建一個包含您的枚舉和類型的字典,並動態創建一個類型。

//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. 

正如你可能會注意到,呼籲Activator.CreateInstance不是高性能。因此,如果您想稍微提高性能,可以將字典更改爲Dictionary<MyEnum,Func<object>>,而不是將類型添加爲值,而是添加包裝每個類的構造函數的函數,並將它們作爲對象返回。

編輯:我加入一個ConstructorFactory類,改編自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); 
} 

你可以用它來替代Activator.CreateInstance,它提供瞭如果結果被緩存更高的性能。

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

我喜歡將這些類型放到字典中然後通過鍵訪問的想法。很好的答案。 –

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; 
        } 
       } 
      } 
     } 
    } 

這樣,你可以指定你的枚舉作爲參數的屬性。實質上,這是一個非常簡單和輕量級的DI容器。 我建議任何更復雜的東西,像使用StructureMap或NInject。

0

可以使用屬性來保存信息,但最終決策過程仍然必須進行,並且可能不會有太大的不同;只是增加了屬性的複雜性。無論您從何處獲得信息以作出決定,決策的性質都保持不變,從現有的三個列舉或屬性中。

尋找一種方法來結合三個枚舉可能會更有成效。

枚舉可以是任何整數類型,因此消除嵌套(和冗餘)開關的最簡單方法是將枚舉組合在一起。如果枚舉是標誌值的集合,這是最簡單的。也就是說,枚舉的每個值都具有二進制字符串中單個位的值(第一位爲1,第二位爲2,第三位爲8,16等)。

假設每個枚舉的值可以連接在一起,它將選擇過程減少爲單個switch語句。這可以通過連接,相乘或者增加枚舉值來完成 - 但是它們如何連接在一起取決於枚舉,並且在不知道更多細節的情況下很難提供更明確的方向。

1

另一種解決方案是使用依賴注入(DI)容器。例如使用Unity DI您可以:使用

// 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()); 

例子統一: Implementing the Microsoft Unity (Dependency Injection) Design Pattern

當然你可以使用任何DI容器(溫莎城堡,StructureMap,Ninject有,這是一個列表一些可用的。 .NET DI容器List of .NET Dependency Injection Containers (IOC)