扩展枚举,矫枉过正?

时间:2010-06-10 15:33:32

标签: c# enums attributes extension-methods

我有一个需要序列化为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<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);
        }
} 

最后,对于用户界面,GetDisplayText()用于显示用户友好的文字。

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

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

8 个答案:

答案 0 :(得分:7)

您用于序列化的部分很好。反序列化部分写得很笨拙。主要问题是你使用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);
}

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

答案 1 :(得分:4)

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

答案 2 :(得分:3)

为什么不创建一个具有静态成员的类型,例如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)

......班级支持领域......

.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

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

恕我直言,设计很扎实,并且会起作用。 但是,反射往往很慢,所以如果在紧密循环中使用这些方法,它可能会减慢整个应用程序。

您可以尝试将返回值缓存到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];
    }

答案 6 :(得分:1)

不能说我见过这样做但消费者代码相对简单,所以我很乐意使用它。

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

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

并将其替换为

return attribs[0].StringValue;

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

答案 7 :(得分:1)

我知道这个问题已经得到解答,但是之前我发布了以下代码片段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
    }
}