如何在C#中使用自定义属性来替换switch语句中的switch语句?

5
我有一个工厂方法,根据三个枚举值返回正确的子类。 一种做法是使用嵌套的switch语句。显然,我不太喜欢这个选项。
我想到的另一种选项是在C#中使用属性。每个子类都会有一个包含这3个枚举值的属性,在工厂中,我只需要获取具有相同枚举值的类,与工厂中枚举值相对应。
然而,我对属性还比较陌生,在网上没有找到合适的解决方案。如果有人能给我一些提示或几行代码,我真的会非常感激!

你能用一个2乘2的情况(每个开关中有2个枚举值)来说明问题吗? - O. R. Mapper
3
我认为即使你在子类上使用属性,你仍然需要使用嵌套的开关语句。而且你还需要使用反射来获取所有子类的属性值,这会很慢。 - 24x7Programmer
5个回答

4
首先,声明您的属性并将其添加到您的类中。
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]();

我喜欢将类型放入字典中,然后通过键访问的想法。非常好的答案。 - Jamie Howarth

2

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 的东西。


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

使用Unity的示例: 实现Microsoft Unity(依赖注入)设计模式

当然,您可以使用任何DI容器(Castle Windsor、StructureMap、Ninject)。这里是一些可用的.NET DI容器列表.NET依赖注入容器(IOC)列表


0

可以使用属性来保存信息,但最终决策过程仍然需要进行,并且可能并没有太大的区别;只是增加了属性的复杂性。无论您从现有的三个枚举还是从属性中获取信息来做出决策,决策的本质都是相同的。

寻找一种将三个枚举组合在一起的方法可能会更有成效。

枚举可以是任何整数类型,因此消除嵌套(和冗余)开关的最简单方法是将枚举值组合在一起。如果枚举是标志值的集合,则这是最简单的方法。也就是说,枚举的每个值都具有二进制字符串中单个位的值(第一位为1,第二位为2,第三位为4,第8、16等等)。

只要每个枚举的值可以连接在一起,它就可以将选择过程减少到一个switch语句。这可能最好通过连接、乘法或加法枚举值来完成,但如何将它们连接在一起取决于枚举,如果没有更多详细信息,很难提供更明确的方向。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接