代码之家  ›  专栏  ›  技术社区  ›  CkH

扩展枚举,过度杀戮?

  •  4
  • CkH  · 技术社区  · 16 年前

    我有一个对象需要序列化为EDI格式。在这个例子中,我们会说它是一辆汽车。汽车可能不是最好的例子,b/c选项会随着时间的推移而改变,但对于真实对象,枚举永远不会改变。

    我有许多枚举,如下面的自定义属性应用。

    public enum RoofStyle
    {
        [DisplayText("Glass Top")]
        [StringValue("GTR")]
        Glass,
        [DisplayText("Convertible Soft Top")]
        [StringValue("CST")]
        ConvertibleSoft,
        [DisplayText("Hard Top")]
        [StringValue("HT ")]
        HardTop,
        [DisplayText("Targa Top")]
        [StringValue("TT ")]
        Targa,
    }
    

    public static string GetStringValue(this Enum value)
    {
        // Get the type
        Type type = value.GetType();
    
        // Get fieldinfo for this type
        FieldInfo fieldInfo = type.GetField(value.ToString());
    
        // Get the stringvalue attributes
        StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
            typeof(StringValueAttribute), false) as StringValueAttribute[];
    
        // Return the first if there was a match.
        return attribs.Length > 0 ? attribs[0].StringValue : null;
    }
    
    public static string GetDisplayText(this Enum value)
    {
        // Get the type
        Type type = value.GetType();
    
        // Get fieldinfo for this type
        FieldInfo fieldInfo = type.GetField(value.ToString());
    
        // Get the DisplayText attributes
        DisplayTextAttribute[] attribs = fieldInfo.GetCustomAttributes(
            typeof(DisplayTextAttribute), false) as DisplayTextAttribute[];
    
        // Return the first if there was a match.
        return attribs.Length > 0 ? attribs[0].DisplayText : value.ToString();
    }
    

        StringBuilder sb = new StringBuilder();
        sb.Append(car.RoofStyle.GetStringValue());
        sb.Append(car.TireSize.GetStringValue());
        sb.Append(car.Model.GetStringValue());
        ...
    

    还有另一种方法可以从StringValue获取枚举值以进行反序列化:

       car.RoofStyle = Enums.GetCode<RoofStyle>(EDIString.Substring(4, 3))
    

    定义为:

    public static class Enums
        {
            public static T GetCode<T>(string value)
            {
                foreach (object o in System.Enum.GetValues(typeof(T)))
                {
                    if (((Enum)o).GetStringValue() == value.ToUpper())
                        return (T)o;
                }
                throw new ArgumentException("No code exists for type " + typeof(T).ToString() + " corresponding to value of " + value);
            }
    } 
    

    最后,对于UI GetDisplayText() 用于显示用户友好的文本。

    你怎么认为?过度杀戮?有更好的办法吗?还是戈尔迪洛克(恰到好处)?

    8 回复  |  直到 16 年前
        1
  •  7
  •   Aaronaught    16 年前

    ToUpper() 比较容易断开的字符串(想想全球化)。这样的比较应该与 string.Compare 相反,或者 string.Equals overload StringComparison

    另一件事是在反序列化过程中一次又一次地执行这些查找会非常慢。如果要序列化大量数据,这实际上是非常明显的。在这种情况下,您需要从 StringValue 到枚举本身-将其放入静态 Dictionary<string, RoofStyle>

    public static class Enums
    {
        private static Dictionary<string, RoofStyle> roofStyles =
            new Dictionary<string, RoofStyle>()
        {
            { "GTR", RoofStyle.Glass },
            { "CST", RoofStyle.ConvertibleSoft },
            { "HT ", RoofStyle.HardTop },
            { "TT ", RoofStyle.TargaTop }
        }
    
        public static RoofStyle GetRoofStyle(string code)
        {
            RoofStyle result;
            if (roofStyles.TryGetValue(code, out result))
                return result;
            throw new ArgumentException(...);
        }
    }
    

    它不是“通用的”,但它的方式更有效。如果您不喜欢字符串值的重复,那么将代码提取为单独类中的常量。

    static Dictionary<string, T> CreateEnumLookup<T>()
    {
        return Enum.GetValues(typeof(T)).ToDictionary(o => ((Enum)o).GetStringValue(),
            o => (T)o);
    }
    

    请注意一些小细节,但您可能需要考虑使用 Attribute.GetCustomAttribute 而不是 MemberInfo.GetCustomAttributes 如果你只期望有一个属性。当你只需要一件物品的时候,没有理由去摆弄所有的数组。

        2
  •  4
  •   mikerobi    16 年前

    就我个人而言,我认为你在滥用语言,并试图以一种他们从未想过的方式使用枚举。我将创建一个静态类RoofStyle,创建一个简单的struct RoofType,并为每个枚举值使用一个实例。

        3
  •  3
  •   Matthew Whited    16 年前

    为什么不创建一个带有静态成员的类型,比如mikerobi说的

    例子。。。

    public class RoofStyle
    {
        private RoofStyle() { }
        public string Display { get; private set; }
        public string Value { get; private set; }
    
        public readonly static RoofStyle Glass = new RoofStyle
        {
            Display = "Glass Top",  Value = "GTR",
        };
        public readonly static RoofStyle ConvertibleSoft = new RoofStyle
        {
            Display = "Convertible Soft Top", Value = "CST",
        };
        public readonly static RoofStyle HardTop = new RoofStyle
        {
            Display = "Hard Top", Value = "HT ",
        };
        public readonly static RoofStyle Targa = new RoofStyle
        {
            Display = "Targa Top", Value = "TT ",
        };
    }
    

    顺便说一句。。。

    ... 枚举支持字段。。。

    .field public specialname rtspecialname int32 value__
    .field public static literal valuetype A.ERoofStyle Glass = int32(0x00)
    .field public static literal valuetype A.ERoofStyle ConvertibleSoft = int32(0x01)
    .field public static literal valuetype A.ERoofStyle HardTop = int32(0x02)
    .field public static literal valuetype A.ERoofStyle Targa = int32(0x03)
    

    .field public static initonly class A.RoofStyle Glass
    .field public static initonly class A.RoofStyle ConvertibleSoft
    .field public static initonly class A.RoofStyle HardTop
    .field public static initonly class A.RoofStyle Targa
    
        4
  •  2
  •   Bryan Watts    16 年前

    下面是我用于枚举类的基类:

    public abstract class Enumeration<T, TId> : IEquatable<T> where T : Enumeration<T, TId>
    {
        public static bool operator ==(Enumeration<T, TId> x, T y)
        {
            return Object.ReferenceEquals(x, y) || (!Object.ReferenceEquals(x, null) && x.Equals(y));
        }
    
        public static bool operator !=(Enumeration<T, TId> first, T second)
        {
            return !(first == second);
        }
    
        public static T FromId(TId id)
        {
            return AllValues.Where(value => value.Id.Equals(id)).FirstOrDefault();
        }
    
        public static readonly ReadOnlyCollection<T> AllValues = FindValues();
    
        private static ReadOnlyCollection<T> FindValues()
        {
            var values =
                (from staticField in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public)
                where staticField.FieldType == typeof(T)
                select (T) staticField.GetValue(null))
                .ToList()
                .AsReadOnly();
    
            var duplicateIds =
                (from value in values
                group value by value.Id into valuesById
                where valuesById.Skip(1).Any()
                select valuesById.Key)
                .Take(1)
                .ToList();
    
            if(duplicateIds.Count > 0)
            {
                throw new DuplicateEnumerationIdException("Duplicate ID: " + duplicateIds.Single());
            }
    
            return values;
        }
    
        protected Enumeration(TId id, string name)
        {
            Contract.Requires(((object) id) != null);
            Contract.Requires(!String.IsNullOrEmpty(name));
    
            this.Id = id;
            this.Name = name;
        }
    
        protected Enumeration()
        {}
    
        public override bool Equals(object obj)
        {
            return Equals(obj as T);
        }
    
        public override int GetHashCode()
        {
            return this.Id.GetHashCode();
        }
    
        public override string ToString()
        {
            return this.Name;
        }
    
        #region IEquatable
    
        public virtual bool Equals(T other)
        {
            return other != null && this.IdComparer.Equals(this.Id, other.Id);
        }
        #endregion
    
        public virtual TId Id { get; private set; }
    
        public virtual string Name { get; private set; }
    
        protected virtual IEqualityComparer<TId> IdComparer
        {
            get { return EqualityComparer<TId>.Default; }
        }
    }
    

    public sealed class RoofStyle : Enumeration<RoofStyle, int>
    {
        public static readonly RoofStyle Glass = new RoofStyle(0, "Glass Top", "GTR");
        public static readonly RoofStyle ConvertibleSoft = new RoofStyle(1, "Convertible Soft Top", "CST");
        public static readonly RoofStyle HardTop = new RoofStyle(2, "Hard Top", "HT ");
        public static readonly RoofStyle Targa = new RoofStyle(3, "Targa Top", "TT ");
    
        public static RoofStyle FromStringValue(string stringValue)
        {
            return AllValues.FirstOrDefault(value => value.StringValue == stringValue);
        }
    
        private RoofStyle(int id, string name, string stringValue) : base(id, name)
        {
            StringValue = stringValue;
        }
    
        public string StringValue { get; private set; }
    }
    

    您可以在序列化过程中使用它,如下所示:

    var builder = new StringBuilder();
    
    builder.Append(car.RoofStyle.StringValue);
    ...
    

    要反序列化:

    car.RoofStyle = RoofStyle.FromStringValue(EDIString.Substring(4, 3));
    
        5
  •  1
  •   Femaref    16 年前

    我不觉得有什么问题-事实上,我也是这么做的。通过这种方式,我实现了枚举的详细性,并且可以定义在使用枚举请求数据时如何转换枚举,例如RequestTarget.Character将导致“char”。

        6
  •  1
  •   Steven Evers    16 年前

    不能说我见过这样做,但消费者代码相对简单,所以我可能会喜欢使用它。

    对我来说,唯一突出的是消费者必须处理空值的可能性-这可能是可以删除的。如果您可以控制这些属性(从它的发音来看,您可以这样做),那么就不应该出现GetDisplayText或GetStringValue返回null的情况,这样您就可以删除它们

    return attribs.Length > 0 ? attribs[0].StringValue : null;
    

    return attribs[0].StringValue;
    

    为了简化用户代码的接口。

        7
  •  1
  •   SWeko    16 年前

    但是,反射的速度往往很慢,因此如果在紧密循环中使用这些方法,可能会减慢整个应用程序的速度。

    您可以尝试将返回值缓存到 Dictionary<RoofStyle, string> 所以它们只反射一次,然后从缓存中提取。

        private static Dictionary<Enum, string> stringValues 
          = new Dictionary<Enum,string>();
    
        public static string GetStringValue(this Enum value)
        {
            if (!stringValues.ContainsKey(value))
            {
                Type type = value.GetType();
                FieldInfo fieldInfo = type.GetField(value.ToString());
                StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
                    typeof(StringValueAttribute), false) as StringValueAttribute[];
                stringValues.Add(value, attribs.Length > 0 ? attribs[0].StringValue : null);
            }
            return stringValues[value];
        }
    
        8
  •  1
  •   Ed Courtenay    16 年前

    我知道这个问题已经得到了回答,但是不久前我发布了以下代码片段 on my personal blog ,它演示了如何使用扩展方法伪造Java样式的枚举。您可能会发现此方法非常适合您,特别是它克服了通过反射访问属性的开销。

    using System;
    using System.Collections.Generic;
    
    namespace ScratchPad
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                var p = new Program();
                p.Run();
            }
    
        private void Run()
        {
            double earthWeight = 175;
            double mass = earthWeight / Planet.Earth.SurfaceGravity();
    
            foreach (Planet planet in Enum.GetValues(typeof(Planet))) {
                Console.WriteLine("Your weight on {0} is {1}", planet, planet.SurfaceWeight(mass));
            }
        }
    }
    
    public enum Planet
    {
        Mercury,
        Venus,
        Earth,
        Mars,
        Jupiter,
        Saturn,
        Uranus,
        Neptune
    }
    
    public static class PlanetExtensions
    {
        private static readonly Dictionary<Planet, PlanetData> planetMap = new Dictionary<Planet, PlanetData>
          {
              {Planet.Mercury, new PlanetData(3.303e+23, 2.4397e6)},
              {Planet.Venus, new PlanetData(4.869e+24, 6.0518e6)},
              {Planet.Earth, new PlanetData(5.976e+24, 6.37814e6)},
              {Planet.Mars, new PlanetData(6.421e+23, 3.3972e6)},
              {Planet.Jupiter, new PlanetData(1.9e+27,   7.1492e7)},
              {Planet.Saturn, new PlanetData(5.688e+26, 6.0268e7)},
              {Planet.Uranus, new PlanetData(8.686e+25, 2.5559e7)},
              {Planet.Neptune, new PlanetData(1.024e+26, 2.4746e7)}
          };
    
        private const double G = 6.67300E-11;
    
        public static double Mass(this Planet planet)
        {
            return GetPlanetData(planet).Mass;
        }
    
        public static double Radius(this Planet planet)
        {
            return GetPlanetData(planet).Radius;
        }
    
        public static double SurfaceGravity(this Planet planet)
        {
            PlanetData planetData = GetPlanetData(planet);
    
            return G * planetData.Mass / (planetData.Radius * planetData.Radius);
        }
    
        public static double SurfaceWeight(this Planet planet, double mass)
        {
            return mass * SurfaceGravity(planet);
        }
    
        private static PlanetData GetPlanetData(Planet planet)
        {
            if (!planetMap.ContainsKey(planet))
                throw new ArgumentOutOfRangeException("planet", "Unknown Planet");
    
            return planetMap[planet];
        }
    
        #region Nested type: PlanetData
    
        public class PlanetData
        {            
            public PlanetData(double mass, double radius)
            {
                Mass = mass;
                Radius = radius;
            }
    
            public double Mass { get; private set; }
            public double Radius { get; private set; }
        }
    
        #endregion
        }
    }