我有一个工厂方法,它根据三个枚举值返回正确的子类。一种方法是在开关中使用开关。显然,我不太喜欢这个选项。
我认为另一种选择是在 C# 中使用属性。每个子类都有一个具有这 3 个枚举值的属性,在工厂中,我只需要获取与我在工厂中拥有的枚举值对应的枚举值相同的类。
但是,我对属性很陌生,我没有在网上找到任何合适的解决方案。如果有人能给我一些提示或一些代码行,我真的很感激!
我有一个工厂方法,它根据三个枚举值返回正确的子类。一种方法是在开关中使用开关。显然,我不太喜欢这个选项。
我认为另一种选择是在 C# 中使用属性。每个子类都有一个具有这 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
类,改编自此页面。
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]();
看看这篇文章:创建自定义属性。然后,您可以使用反射(例如GetCustomAttributes)来获取属性及其值。
希望这可以帮助
另一种解决方案是使用依赖注入 (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());
使用 Unity 的示例:实现 Microsoft Unity(依赖注入)设计模式
当然,您可以使用任何 DI 容器(Castle Windsor、StructureMap、Ninject。这里列出了一些可用的 .NET DI 容器List of .NET Dependency Injection Containers (IOC)
[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 之类的东西。
可以使用属性来保存信息,但最终仍需要做出决策过程,并且可能不会有太大不同;只是增加了属性的复杂性。无论您从哪里获得信息来做出决定,无论是从现有的三个枚举还是从属性中,决策的性质都保持不变。
寻找一种结合这三个枚举的方法可能会更有成效。
枚举可以是任何整数类型,因此消除嵌套(和冗余)开关的最简单方法是将枚举组合在一起。如果枚举是标志值的集合,这是最简单的。也就是说,枚举的每个值都具有二进制字符串中单个位的值(第一位为 1,第二位为 2,第三位为 4,8、16 等等)。
如果每个枚举的值可以连接在一起,它会将选择过程减少到单个 switch 语句。这可能最好通过连接、相乘或相加枚举值来完成——但是它们如何连接在一起取决于枚举,并且在不了解更多细节的情况下很难提供更明确的方向。