扩展枚举,矫枉过正?

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

我有许多枚举,如下所示应用了自定义属性。

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

有一个自定义EDI序列化程序,它基于StringValue属性进行序列化,如下所示:

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

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

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

定义为:

 public static class Enums { public static T GetCode(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()用于显示用户友好的文本。

你怎么看? 矫枉过正? 有没有更好的办法? 还是Goldie Locks(恰到好处)?

只是想在我将其永久地集成到我的个人框架中之前得到反馈。 谢谢。

您用于序列化的部分很好。 反序列化部分写得很笨拙。 主要的问题是你使用ToUpper()来比较字符串,这很容易被打破(想想全球化)。 这样的比较应该用string.Compare来完成,或者是string.Equals重载 ,它接受StringComparison

另一件事是在反序列化期间一次又一次地执行这些查找将会非常缓慢。 如果你要序列化大量数据,这实际上可能非常明显。 在这种情况下,您需要构建一个从StringValue到枚举本身的映射 – 将其放入静态Dictionary并将其用作往返的查找。 换一种说法:

 public static class Enums { private static Dictionary roofStyles = new Dictionary() { { "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 CreateEnumLookup() { return Enum.GetValues(typeof(T)).ToDictionary(o => ((Enum)o).GetStringValue(), o => (T)o); } 

PS次要细节,但如果您只希望有一个属性,则可能需要考虑使用Attribute.GetCustomAttribute而不是MemberInfo.GetCustomAttributes 。 当你只需要一个项目时,没有理由让所有arrays摆弄。

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

你为什么不创建一个静态成员类型,如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 ", }; } 

BTW …

当编译成IL时,Enum与此类结构非常相似。

…… Enum支持领域……

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

……class级支持领域……

 .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 

这是我用于枚举类的基类:

 public abstract class Enumeration : IEquatable where T : Enumeration { public static bool operator ==(Enumeration x, T y) { return Object.ReferenceEquals(x, y) || (!Object.ReferenceEquals(x, null) && x.Equals(y)); } public static bool operator !=(Enumeration 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 AllValues = FindValues(); private static ReadOnlyCollection 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 IdComparer { get { return EqualityComparer.Default; } } } 

实现看起来像:

 public sealed class RoofStyle : Enumeration { 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)); 

我没有看到它的问题 – 实际上,我也这样做。 通过这个,我用枚举实现了冗长,并且可以定义当我使用它来请求数据时如何翻译它,例如。 RequestTarget.Character将导致“char”。

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

对我而言,唯一可以解决的问题是消费者可能需要处理空值 – 这可能会被删除。 如果您可以控制属性(您可以从它的声音中进行控制),那么应该永远不会出现GetDisplayText或GetStringValue返回null的情况,因此您可以删除

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

并替换它

 return attribs[0].StringValue; 

为了简化消费者代码的界面。

恕我直言,设计是坚实的,并将工作。 但是,reflection往往很慢,所以如果这些方法用于紧密循环,它可能会减慢整个应用程序的速度。

您可以尝试将返回值缓存到Dictionary以便它们只反映一次,然后从缓存中获取。

像这样的东西:

  private static Dictionary stringValues = new Dictionary(); 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]; } 

我知道这个问题已经得到了解答,但是之前我在我的个人博客上发布了以下代码片段,它演示了使用扩展方法伪造Java样式的枚举。 您可能会发现此方法适合您,特别是因为它克服了通过reflection访问属性的开销。

 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 planetMap = new Dictionary { {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 } }