2013-01-03 16 views
5

Tôi có phương thức factory trả về lớp con đúng tùy thuộc vào ba giá trị enum. Một cách để làm là sử dụng công tắc trong công tắc trong công tắc. Rõ ràng, tôi không thích lựa chọn đó lắm.Làm cách nào để sử dụng các thuộc tính tùy chỉnh trong C# để thay thế các nút chuyển trong công tắc?

Tôi nghĩ rằng một tùy chọn khác sẽ là sử dụng các thuộc tính trong C#. Mỗi lớp con sẽ có một thuộc tính với 3 giá trị enum đó và trong nhà máy, tôi chỉ phải có lớp có cùng giá trị enum tương ứng với các giá trị enum mà tôi có trong nhà máy.

Tuy nhiên, tôi khá mới đối với thuộc tính và tôi không tìm thấy giải pháp phù hợp nào trên web. Nếu có ai, có thể cho tôi một số gợi ý hoặc một số dòng mã, tôi thực sự sẽ đánh giá cao điều đó!

+0

Bạn có thể minh họa sự cố với tình huống 2 lần 2 (chuyển đổi trong mỗi chuyển đổi với 2 giá trị enum) không? –

+3

Tôi nghĩ ngay cả khi bạn sử dụng thuộc tính trên lớp con, bạn sẽ phải sử dụng công tắc trong công tắc trong công tắc. bạn sẽ phải sử dụng sự phản chiếu để lấy các giá trị thuộc tính cho tất cả các lớp con và nó sẽ chậm. – 24x7Programmer

Trả lời

3

Trước hết, hãy khai báo thuộc tính của bạn và thêm thuộc tính vào lớp học của bạn.

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

Sau đó, bạn có thể sử dụng mã này để tạo từ điển với enums và các loại và tạo kiểu động.

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

Như bạn có thể nhận thấy, gọi Activator.CreateInstance không hoạt động. Do đó, nếu bạn muốn cải thiện hiệu suất một chút, bạn có thể thay đổi từ điển thành Dictionary<MyEnum,Func<object>> và thay vì thêm các loại làm giá trị bạn sẽ thêm hàm bao hàm hàm tạo của mỗi lớp và trả về dưới dạng đối tượng.

EDIT: Tôi đang thêm một lớp ConstructorFactory, được điều chỉnh từ trang 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); 
} 

Bạn có thể sử dụng nó như một thay thế cho Activator.CreateInstance, nó cung cấp hiệu suất cao hơn nếu kết quả được lưu trữ.

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

Tôi thích ý tưởng đưa các loại vào từ điển để truy cập bằng khóa. Câu trả lời chính xác. –

2

Hãy xem bài viết này: Creating Custom Attributes. Sau đó, bạn có thể sử dụng sự phản chiếu (ví dụ GetCustomAttributes) để lấy các thuộc tính và giá trị của chúng.

Hy vọng điều này sẽ giúp

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

Bằng cách này bạn có thể chỉ định enum của bạn như một tham số vào thuộc tính. Về bản chất, đây là một container DI rất đơn giản và nhẹ. Tôi muốn đề xuất bất cứ điều gì phức tạp hơn, để sử dụng một cái gì đó như StructureMap hoặc NInject.

0

Có thể sử dụng các thuộc tính để giữ thông tin, nhưng cuối cùng quá trình ra quyết định sẽ vẫn phải được thực hiện và có thể sẽ không khác nhiều; chỉ với sự phức tạp của các thuộc tính. Bản chất của quyết định vẫn giữ nguyên bất kể bạn lấy thông tin ở đâu để đưa ra quyết định, từ ba điều khoản hiện có hoặc từ các thuộc tính.

Nó có thể chứng minh hiệu quả hơn để tìm cách kết hợp ba bảng liệt kê.

Enums có thể là bất kỳ loại tích phân nào, vì vậy cách dễ nhất để loại bỏ các chuyển mạch lồng nhau (và dự phòng) là kết hợp các liệt kê với nhau. Điều này là dễ nhất nếu điều tra là một tập hợp các giá trị cờ. Tức là, mỗi giá trị của liệt kê có giá trị của một bit trong một chuỗi nhị phân (1 cho bit đầu tiên, 2 cho bit thứ hai, 4 cho bit thứ ba, 8, 16 và vv).

Cung cấp các giá trị của mỗi liệt kê có thể được nối với nhau, nó làm giảm quá trình lựa chọn thành một câu lệnh chuyển đổi đơn. Điều này có thể được thực hiện tốt nhất bằng cách nối, nhân hoặc thêm các giá trị liệt kê - nhưng cách chúng được nối với nhau phụ thuộc vào các liệt kê, và không biết thêm chi tiết thì khó có thể cung cấp hướng xác định hơn.

1

Một giải pháp khác là sử dụng vùng chứa Dependency Injection (DI). Ví dụ sử dụng Unity DI bạn có thể:

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

Các ví dụ về việc sử dụng Unity: Implementing the Microsoft Unity (Dependency Injection) Design Pattern

Tất nhiên bạn có thể sử dụng bất kỳ container DI (Lâu đài Windsor, StructureMap, Ninject Dưới đây là danh sách một số có sẵn. NET DI container List of .NET Dependency Injection Containers (IOC)

Các vấn đề liên quan