Как сортировать перечисление с использованием настраиваемого атрибута заказа?

У меня есть перечисление вроде этого:

enum MyEnum{
 [Order(1)]
 ElementA = 1,
 [Order(0)]
 ElementB = 2,
 [Order(2)]
 ElementC = 3
}

И я хочу перечислить его элементы, отсортированные по специальному атрибуту заказа, который я написал, чтобы получить список отсортированных элементов.

Я получаю атрибут описания, но только для одного элемента, подобного этому:

FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

Это может быть что-то одно и то же, но нужно работать на всех Enum и возвращать список или другое отсортированное перечисление.

Ответ 1

Предположим, что класс OrderAttribute выглядит следующим образом:

public class OrderAttribute : Attribute
{
    public readonly int Order;

    public OrderAttribute(int order)
    {
        Order = order;
    }
}

Вспомогательный метод для получения отсортированных значений перечисления:

public static T[] SortEnum<T>()
{
    Type myEnumType = typeof(T);
    var enumValues = Enum.GetValues(myEnumType).Cast<T>().ToArray();
    var enumNames = Enum.GetNames(myEnumType);
    int[] enumPositions = Array.ConvertAll(enumNames, n =>
    {
        OrderAttribute orderAttr = (OrderAttribute)myEnumType.GetField(n)
            .GetCustomAttributes(typeof(OrderAttribute), false)[0];
        return orderAttr.Order;
    });

    Array.Sort(enumPositions, enumValues);

    return enumValues;
}

Ответ 2

Если я четко понял вашу проблему, здесь может быть решение вроде этого:

public static class EnumExtenstions
{
    public static IEnumerable<TEnum> EnumGetOrderedValues<TEnum>(this Type enumType)
    {

        var fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
        var orderedValues = new List<Tuple<int, TEnum>>();
        foreach (var field in fields)
        {
            var orderAtt = field.GetCustomAttributes(typeof(EnumOrderAttribute), false).SingleOrDefault() as EnumOrderAttribute;
            if (orderAtt != null)
            {
                orderedValues.Add(new Tuple<int, TEnum>(orderAtt.Order, (TEnum)field.GetValue(null)));
            }
        }

        return orderedValues.OrderBy(x=>x.Item1).Select(x=>x.Item2).ToList();
    }
}

использование:

var result = typeof(enumType).EnumGetOrderedValues<enumType>();

Ответ 3

Учитывая

[AttributeUsage(AttributeTargets.Field)]
public class OrderAttribute : Attribute
{
    public readonly int Order;

    public OrderAttribute(int order)
    {
        Order = order;
    }
}

public static class OrderHelper
{
    public static int GetOrder<TEnum>(TEnum value) where TEnum : struct
    {
        int order;

        if (!OrderHelperImpl<TEnum>.Values.TryGetValue(value, out order))
        {
            order = int.MaxValue;
        }

        return order;
    }

    private static class OrderHelperImpl<TEnum>
    {
        public static readonly Dictionary<TEnum, int> Values;

        static OrderHelperImpl()
        {
            var values = new Dictionary<TEnum, int>();

            var fields = typeof(TEnum).GetFields(BindingFlags.Static | BindingFlags.Public);

            int unordered = int.MaxValue - 1;

            for (int i = fields.Length - 1; i >= 0; i--)
            {
                FieldInfo field = fields[i];

                var order = (OrderAttribute)field.GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault();

                int order2;

                if (order != null)
                {
                    order2 = order.Order;
                }
                else
                {
                    order2 = unordered;
                    unordered--;
                }

                values[(TEnum)field.GetValue(null)] = order2;
            }

            Values = values;
        }
    }
}

Вы можете:

int o1 = OrderHelper.GetOrder(MyEnum.ElementA);
int o2 = OrderHelper.GetOrder(MyEnum.ElementB);
int o3 = OrderHelper.GetOrder(MyEnum.ElementC);

Итак, сортировка похожа:

var myenums = new[] { MyEnum.ElementA, MyEnum.ElementB, MyEnum.ElementC };
Array.Sort(myenums, (p, q) => OrderHelper.GetOrder(p).CompareTo(OrderHelper.GetOrder(q)));

или для LINQ:

var myenums = new[] { MyEnum.ElementA, MyEnum.ElementB, MyEnum.ElementC };
var sorted = myenums.OrderBy(x => OrderHelper.GetOrder(x));

OrderHelper "кэширует" порядок внутри a OrderHelperImpl<TEnum>. Значения перечисления извлекаются, зная, что в перечислениях значения public static полей (вы можете легко увидеть это здесь).

Значения без Order упорядочены в том же порядке, что и в enum, используя максимально возможные значения int чуть ниже int.MaxValue

Ответ 4

public class OrderAttribute : Attribute
{
    public int priority;

    public OrderAttribute(int priority)
    {
        this.priority = priority;
    }
}

public enum Test
{
    [Order(1)] value1 = 1,
    [Order(2)] value2 = 2,
    [Order(0)] value3 = 3
}

private static void Main(string[] args)
    {
        Dictionary<string, int> priorityTable = new Dictionary<string, int>();

        var values = Enum.GetValues(typeof (Test)).Cast<Test>();
        MemberInfo[] members = typeof (Test).GetMembers();
        foreach (MemberInfo member in members)
        {
            object[] attrs = member.GetCustomAttributes(typeof(OrderAttribute), false);
            foreach (object attr in attrs)
            {
                OrderAttribute orderAttr = attr as OrderAttribute;

                if (orderAttr != null)
                {
                    string propName = member.Name;
                    int priority = orderAttr.priority;

                    priorityTable.Add(propName, priority);
                }
            }
        }

        values = values.OrderBy(n => priorityTable[n.ToString("G")]);

        foreach (var value in values)
        {
            Console.WriteLine(value);
        }

        Console.ReadLine();
    }

Это выведет:

значение3

значение1

значение2