我想创建一个可重用的类来处理Enumerations,以便可以用不同的属性装饰各种Enum,并可以使用该类来显示特定的属性。

UnitTest示例我将如何使用该类:

    internal enum TestEnum
    {
        [System.ComponentModel.Description("Description Attribute")]
        [System.Xml.Serialization.XmlAttribute("XML Attribute")]    
        EnumWithDescription,

        [System.ComponentModel.Category("Display name attribute")]
        [System.ComponentModel.Description("Description Attribute over category attribute")]            
        EnumWithXMLAttribute,

        EnumNoAttributes,
    }

    protected EnumAttributeDescriptor CreateEnumAttributeDescriptor()
    {
        EnumAttributeDescriptor descriptor = new EnumAttributeDescriptor();

        descriptor.AddAttributePriority<System.ComponentModel.DescriptionAttribute>("Description");
        descriptor.AddAttributePriority(new PropertyDecoration("Category", typeof(System.ComponentModel.CategoryAttribute)));            

        return descriptor;
    }

    internal class IntAttributeDescriptor : PropertyAttributeDescriptor
    {
        public IntAttributeDescriptor()
            : base() 
        {
            AddAttributePriority<System.ComponentModel.DescriptionAttribute>("Description");
        }
    }

    internal class EnumSwitchableDescriptor : IObjectDescriptor
    {
        public string GetDescription(object record)
        {
            TestEnum item = (TestEnum)record;

            switch (item)
            {
                case TestEnum.EnumWithDescription:
                    return "This is a test attribute";
                case TestEnum.EnumWithXMLAttribute:
                    return "How did you sneak in here";
                default:
                    throw new NotSupportedException(string.Format("Enumeration not supported {0}", item));
            }  
        }
    }

    internal class TestObject
    {
        [System.ComponentModel.Description("Testable integer")]
        public int TestInteger
        {
            get;
            set;
        }
    }

    [TestMethod]
    public void EnumDescriptionHardCodedTest()
    {
        ObjectDescription descriptions = new ObjectDescription(new EnumSwitchableDescriptor());
        string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);

        Assert.AreEqual("This is a test attribute", name);
    }

    [TestMethod]
    public void EnumDescriptionMissingAttributeTest()
    {
        ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());

        string name = descriptions.GetDisplayName(TestEnum.EnumNoAttributes);

        Assert.IsNull(name);
    }

    [TestMethod]
    public void EnumDescriptionDefaultAttributeTest()
    {
        ObjectDescription descriptions = new ObjectDefaultableDescription((obj) => { return null; });

        string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);

        Assert.AreEqual("EnumWithDescription", name);
    }

    [TestMethod]
    public void EnumDescriptionFromSingleAttributeTest()
    {
        ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());

        string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);

        Assert.AreEqual("Description Attribute", name);
    }

    [TestMethod]
    public void EnumDescriptionFromObjectTest()
    {
        ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());

        TestEnum testable = TestEnum.EnumWithXMLAttribute;
        string name = descriptions.GetDisplayName(testable);

        Assert.AreEqual("Description Attribute over category attribute", name);
    }

    [TestMethod]
    public void IntDescriptionFromObjectTest()
    {
        TestObject testable = new TestObject();
        ObjectDescription descriptor = new ObjectDescription(new IntAttributeDescriptor());

        string description = descriptor.GetDisplayName(testable);

        Assert.AreEqual("Testable integer", description);
    }


我负责获取属性的ObjectInspector类:

public class ObjectInspector
{
    public string GetProperty(object record, string propertyName)
    {
        Type typeOf = record.GetType();

        object value = typeOf.GetProperty(propertyName).GetValue(record, null);

        if (value == null)
            return null;
        else
            return value.ToString();
    }

    public bool IsOfType(object record, Type typeOf)
    {
        Type typeOfRecord = record.GetType();            
        return typeOfRecord.Equals(typeOf) || typeOfRecord.IsSubclassOf(typeOf);            
    }

    public bool IsOfType<T>(object record)
    {
        Type typeOf = typeof(T); 
        return IsOfType(record, typeOf);
    }

}


>我的描述符类和接口:

public interface IObjectDescriptor
{
    string GetDescription(object record);
}

public abstract class AttributeDescriptor : IObjectDescriptor
{
    private readonly List<PropertyDecoration> _attributes;

    public AttributeDescriptor() 
    {
        _attributes = new List<PropertyDecoration>();
    }

    public string GetDescription(object record)
    {
        var priorities = GetAttributes();

        if (priorities == null || record == null)
            return null;

        var attributes = GetAttributes(record);

        if (attributes != null)
            return GetAttributeDescription(attributes);
        else
            return null;
    }

    public virtual void AddAttributePriority(PropertyDecoration attr)
    {
        _attributes.Add(attr);
    }

    public virtual void AddAttributePriority<T>(string propertyName)
    {
        PropertyDecoration lookup = new PropertyDecoration(propertyName, typeof(T));
        AddAttributePriority(lookup);
    }

    public virtual IEnumerable<PropertyDecoration> GetAttributes()
    {
        return _attributes;
    }

    protected abstract object[] GetAttributes(object record);

    protected virtual string GetAttributeDescription(object[] attributes)
    {
        ObjectInspector inspector = new ObjectInspector();
        object attribute = null;

        // looping from highest precedence
        foreach (var attr in GetAttributes())
        {
            // find a matching attribute on our object
            attribute = attributes.FirstOrDefault(p => inspector.IsOfType(p, attr.GetPropertyType()));

            if (attribute != null)
            {
                //return attribute;
                return attr.GetDescription(attribute);
            }
        }

        return null;
    }
}

public class EnumAttributeDescriptor : AttributeDescriptor
{
    protected override object[] GetAttributes(object record)
    {
        var typeOf = record.GetType();
        var info = typeOf.GetMember(record.ToString());
        return info[0].GetCustomAttributes(false);
    }
}


最后是对象描述类,以实际获得描述:

public class ObjectDescription
{
    private IObjectDescriptor _objectDescriptor;

    public ObjectDescription(IObjectDescriptor attributePriorities)
    {
        _objectDescriptor = attributePriorities;
    }

    public string GetDisplayName(object record)
    {
        return GetDescription(record, _objectDescriptor);
    }

    protected virtual string GetDescription(object record, IObjectDescriptor descriptor)
    {
        return descriptor.GetDescription(record);
    }
}

public class ObjectDefaultableDescription : ObjectDescription
{
    private Func<object, string> _defaultIfEmpty;

    public ObjectDefaultableDescription(Func<object, string> defaultIfEmpty)
        : base(null)
    {
        _defaultIfEmpty = defaultIfEmpty;
    }

    protected override string GetDescription(object record, IObjectDescriptor descriptor)
    {         
        if (record == null)
            return _defaultIfEmpty(record);
        else
            return record.ToString();
    }

    private string GetDefaultIfEmpty(object record)
    {
        if (_defaultIfEmpty != null)
            return _defaultIfEmpty(record);
        else
            return null;
    }
}


我想保持它的灵活性,以便随着需求的变化,我可以用不同的属性装饰枚举,即结合使用System.Xml.Serialization.XmlEnumAttribute和System.ComponentModel.DescriptionAttribute,以便UI可以使用Description属性来显示一个很好的名称,而XML序列化程序可以使用XmlEnumAttribute等

我希望在我的v上为此提供一个包装器类繁琐的项目,这样我就不必在任何地方都创建委托方法,但是不确定这种方法。

编辑:重新查看后,我认为GetProperty()方法非常通用,实际上并不是必需只属于一个Enum,所以我将其移到了它自己的ObjectInspector类中。

编辑:经过进一步思考,我决定实际上它与Enum并没有直接关系,而是获得描述的功能从一般属性。我对类进行了一些调整,以尝试将其分开。任何评论将不胜感激。

评论

目前无法访问VS2010,为什么不从weblogs.asp.net/grantbarrington/archive/2009/01/19/…窃取代码,而是将签名重做为公共静态字符串GetDescription (Enum en),其中T :什么呢?而不是返回en.ToString();您可以返回null;表示缺少此特定属性的一种方法。一旦完成,您可以添加包装方法,这些方法提供了更便捷的获取所需信息的方法。

@Leonid。嗯,似乎没有提供足够的灵活性。我如何轻松使用category属性,或者Xml AttributeAttribute。这些没有Description属性,因此我无法进行相应的转换?

对...我不在VS2010面前。如果main worker方法可以为您提供所需的一切,而helper方法使它变得更容易怎么办。用法:公共静态GetCategory(enum){return GetCustomAttributevalue (enum,“ Category”);}等。我不喜欢GetEnumAttribute-它决定了您想要什么。我希望能够请求特定的属性,如果不存在,则获取null。然后,我可以在此之上添加一个方法,该方法将以某种优先顺序查找第一个非null属性字符串。

相关:codereview.stackexchange.com/a/5354/3902

@finnw有点,但不完全是。我想用许多属性装饰我的Enum等,而不仅仅是一个。

#1 楼

我没有真正看到XmlAttribute在特定枚举常量上的意义。我认为这将作为TypeConverter枚举本身或枚举类型的属性。我可以看到DisplayName和Description的一些用途。不幸的是,枚举不支持DisplayName。 DynamicObject实际上并没有那么有用,但是也许它会给您一些想法。您可以向类中添加一些其他属性,例如“枚举名称” Desc。我在想这可能对WPF绑定有用,但前提是您将辅助类构造为具有当前值。 (如果您具有当前值,那么我看不到通过非动态包装器获得的收益。)ICustomTypeConverter还提供了一些有趣的功能。这是DynamicObject的传递:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace TestEnumDO
{
    sealed class EnumHelper<T> : DynamicObject, ICustomTypeDescriptor where T: struct // ,System.Enum -- you wished
    {
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return Enum.GetNames(typeof(T));
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            T t;
            if(!Enum.TryParse<T>(binder.Name, out t))
                return base.TryGetMember(binder, out result);
            result = t;
            return true;
        }

        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(typeof(T));
        }

        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(typeof(T));
        }

        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(typeof(T));
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(typeof(T));
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(typeof(T));
        }

        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(typeof(T), editorBaseType);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(typeof(T), attributes);
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(typeof(T));
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            var values = (T[])Enum.GetValues(typeof(T));
            var props = new PropertyDescriptor[values.Length];
            for (int i = 0; i < values.Length; i++)
            {
                props[i] = new EnumPropertyDescriptor(values[i], attributes);
            }
            return new PropertyDescriptorCollection(props, true);
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return GetProperties(null);
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        private class EnumPropertyDescriptor : PropertyDescriptor
        {
            private readonly T _t;
            public EnumPropertyDescriptor(T value, Attribute[] atts):base(Enum.GetName(typeof(T), value), atts)
            {
                _t = value;
            }

            public override AttributeCollection Attributes
            {
                get
                {
                    var attributeDefs = typeof(T).GetMember(Name).Single().CustomAttributes;
                    var attributes = new List<Attribute>();
                    foreach(var ad in attributeDefs){
                        var constructorArgs = ad.ConstructorArguments.Select(ca => ca.Value).ToArray<object>();
                        var attribute = (Attribute)ad.Constructor.Invoke(constructorArgs);
                        foreach (var na in ad.NamedArguments)
                        {
                            if (na.MemberInfo is PropertyInfo)
                                ((PropertyInfo)na.MemberInfo).SetValue(attribute, na.TypedValue.Value);
                            else if (na.MemberInfo is FieldInfo)
                                ((FieldInfo)na.MemberInfo).SetValue(attribute, na.TypedValue.Value);
                            else throw new NotImplementedException();
                        }
                        attributes.Add(attribute);
                    }
                    return new AttributeCollection(attributes.ToArray());
                }
            }

            public override bool CanResetValue(object component)
            {
                return false;
            }

            public override Type ComponentType
            {
                get { return typeof(T); }
            }

            public override object GetValue(object component)
            {
                return _t;
            }

            public override bool IsReadOnly
            {
                get { return true; }
            }

            public override Type PropertyType
            {
                get { return typeof(T); }
            }

            public override void ResetValue(object component)
            {
            }

            public override void SetValue(object component, object value)
            {
                throw new NotSupportedException();
            }

            public override bool ShouldSerializeValue(object component)
            {
                return true;
            }
        }
    }

    class Program
    {
        enum TestEnum
        {
            [Description("Go One")]
            Go1,
            [Description("Go Two")]
            Go2,
            Go3
        }

        static void Main(string[] args)
        {
            dynamic helper = new EnumHelper<TestEnum>();
            Console.WriteLine("Names:");
            foreach (var name in helper.GetDynamicMemberNames())
                Console.WriteLine("  " + name);

            Console.WriteLine();
            Console.WriteLine("Display Names:");

            var pdc = TypeDescriptor.GetProperties(helper) as PropertyDescriptorCollection;
            var desc = pdc.OfType<PropertyDescriptor>().Single(pd => pd.Name == "Go1").Description;
            Console.WriteLine("Go1 Value = {0}, Description = {1}", (int)helper.Go1, desc);

            if (Debugger.IsAttached)
            {
                Console.WriteLine();
                Console.WriteLine("Press any key...");
                Console.ReadKey();
            }
        }
    }
}


评论


\ $ \ begingroup \ $
布兰农干杯。 xmlAttribute用于将枚举用于序列化并且您要为其提供不同的xml值。我们必须同时使用该属性和描述来为用户提供不同的显示值。
\ $ \ endgroup \ $
– dreza
2012年10月30日19:03

\ $ \ begingroup \ $
我仍然认为枚举声明中的TypeConverter比XmlAttribute更合适,但是我不确定您使用的是什么解串器,以及它是否支持TypeConverters。
\ $ \ endgroup \ $
–布兰农
2012年10月31日,下午3:22

\ $ \ begingroup \ $
System.Xml.Serialization.XmlSerializer
\ $ \ endgroup \ $
– dreza
2012年10月31日下午5:19