Extending Enums, Overkill?

2019-01-25 20:38发布

I have an object that needs to be serialized to an EDI format. For this example we'll say it's a car. A car might not be the best example b/c options change over time, but for the real object the Enums will never change.

I have many Enums like the following with custom attributes applied.

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,
}

The Attributes are accessed via Extension methods:

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

There is a custom EDI serializer that serializes based on the StringValue attributes like so:

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

There is another method that can get Enum Value from StringValue for Deserialization:

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

Defined as:

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

And Finally, for the UI, the GetDisplayText() is used to show the user friendly text.

What do you think? Overkill? Is there a better way? or Goldie Locks (just right)?

Just want to get feedback before I intergrate it into my personal framework permanently. Thanks.

8条回答
爷的心禁止访问
2楼-- · 2019-01-25 21:01

I know this question has already been answered, but while ago I posted the following code fragment on my personal blog, which demonstrates faking Java style enums using extension methods. You might find this method works for you, especially as it overcomes the overhead of accessing Attributes via 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<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
    }
}
查看更多
beautiful°
3楼-- · 2019-01-25 21:08

Here is a base class I use for enumeration classes:

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

An implementation would look like:

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

You would use it during serialization like this:

var builder = new StringBuilder();

builder.Append(car.RoofStyle.StringValue);
...

To deserialize:

car.RoofStyle = RoofStyle.FromStringValue(EDIString.Substring(4, 3));
查看更多
登录 后发表回答