Гарантийный порядок свойств в списке соответствует порядку их появления в кодовом файле. - PullRequest
2 голосов
/ 16 августа 2011

У меня есть интерфейс, который определяет метод для возврата IList<PropertyInfo>:

public interface IWriteable
{
    IList<PropertyInfo> WriteableProperties();
}

.
.
Он реализован в различных (разных) классах следующим образом:

public abstract class Foo
{
    private IList<PropertyInfo> _props;

    protected Foo()
    {
        this._props = new List<PropertyInfo>();

        foreach (PropertyInfo p in this.GetType().GetProperties())
        {
            if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
                this._props.Add(p);
        }
    }

    #region IWriteable Members

    public IList<PropertyInfo> WriteableProperties()
    {
        return this._props;
    }

    #endregion
}

public class Bar : Foo
{
    public string A
    {
        get { return "A"; }
    }

    [Writeable()]
    public string B
    {
        get { return "B"; }
    }

    [Writeable()]
    public string C
    {
        get { return "C"; }
    }

    // Snip
}

Обратите внимание на атрибуты, отмечающие пару свойств, так как эти свойства будут добавлены в список.Этот IList будет затем использоваться в другом месте во время некоторых операций записи файла.

Для меня важно, чтобы они располагались в списке в порядке их появления в файле кода.

Однако MSDN заявляет:

Метод GetProperties не возвращает свойства в определенном порядке, например в алфавитном порядке или в порядке объявления.Ваш код не должен зависеть от порядка, в котором возвращаются свойства, потому что этот порядок меняется.

Итак, каков наилучший способ обеспечить добавление каждого PropertyInfo в том порядке, в котором я хотел бы быть?

(Я также использую .NET2.0, поэтому я не могу использовать какие-либо достоинства Linq, если есть что-то, что могло бы помочь, хотя было бы интересно посмотреть.)

Ответы [ 2 ]

6 голосов
/ 17 августа 2011

Добавьте информацию к атрибуту о заказе, затем вы можете использовать ее для обеспечения заказа, например:

[Writeable(Order = 1)]

Так для следующего атрибута:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class WriteableAttribute : Attribute
{
    public int Order { get; set; }
}

Вы можетеполучить упорядоченный выбор свойств следующим образом:

private readonly List<PropertyInfo> _props;

protected Foo()
{
    _props = new List<PropertyInfo>();

    var props = new Dictionary<int, PropertyInfo>();

    foreach (PropertyInfo p in GetType().GetProperties())
    {
        if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
        {
            var attr = (WriteableAttribute)p
                .GetCustomAttributes(typeof(WriteableAttribute))[0];

            props.Add(attr.Order, p);
        }
    }

    _props.AddRange(props.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value));
}

NB Для производственного кода я бы рекомендовал кэшировать информацию о свойствах (например, для каждого типа), поскольку при переносе это будет относительно медленнымдля каждого экземпляра.

Обновление - Кэширование

С некоторыми примерами кэширования поиска и упорядочения свойств:

public static class PropertyReflector
{
    private static readonly object SyncObj = new object();

    private static readonly Dictionary<Type, List<PropertyInfo>> PropLookup =
        new Dictionary<Type, List<PropertyInfo>>();

    public static IList<PropertyInfo> GetWritableProperties(Type type)
    {
        lock (SyncObj)
        {
            List<PropertyInfo> props;

            if (!PropLookup.TryGetValue(type, out props))
            {
                var propsOrder = new Dictionary<int, PropertyInfo>();

                foreach (PropertyInfo p in type.GetProperties())
                {
                    if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
                    {
                        var attr = (WriteableAttribute)p.GetCustomAttributes(
                            typeof(WriteableAttribute), inherit: true)[0];

                        propsOrder.Add(attr.Order, p);
                    }
                }

                props = new List<PropertyInfo>(propsOrder
                    .OrderBy(kvp => kvp.Key)
                    .Select(kvp => kvp.Value));

                PropLookup.Add(type, props);
            }

            return props;
        }
    }
}

Обновление -Нет Linq

Вы можете заменить раздел Linq следующим кодом, чтобы упорядочить свойства и добавить их в кэш:

List<int> order = new List<int>(propsOrder.Keys);
order.Sort();

props = new List<PropertyInfo>();

order.ForEach(i => props.Add(propsOrder[i]));

PropLookup.Add(type, props);

Обновление - Полный Linq

И с использованием полностью решения Linq:

static IList<PropertyInfo> GetWritableProperties(Type type)
{
    lock (SyncObj)
    {
        List<PropertyInfo> props;

        if (!PropLookup.TryGetValue(type, out props))
        {
            props = type.GetProperties()
                .Select(p => new { p, Atts = p.GetCustomAttributes(typeof(WriteableAttribute), inherit: true) })
                .Where(p => p.Atts.Length != 0)
                .OrderBy(p => ((WriteableAttribute)p.Atts[0]).Order)
                .Select(p => p.p)
                .ToList();

            PropLookup.Add(type, props);
        }

        return props;
    }
}
1 голос
/ 17 августа 2011

Некоторое время назад, когда у меня возникла та же проблема, я написал вспомогательный класс для сортировки свойств на основе свойства Order атрибута.Я использовал встроенный DisplayAttribute, но вы можете просто добавить свойство Order к любому атрибуту, который вы пишете.

class FieldSorter : IComparer, IComparer<DisplayAttribute>, IEqualityComparer<DisplayAttribute>
{
    public int Compare(object x, object y)
    {
        return Compare((DisplayAttribute)x, (DisplayAttribute)y);
    }
    public int Compare(DisplayAttribute x, DisplayAttribute y)
    {
        return x.Order.CompareTo(y.Order);
    }
    public bool Equals(DisplayAttribute x, DisplayAttribute y)
    {
        return Compare(x, y) == 0;
    }
    public int GetHashCode(DisplayAttribute obj)
    {
        return obj.GetHashCode();
    }

    public static SortedList<DisplayAttribute, PropertyInfo> GetSortedFields(Type type)
    {
        PropertyInfo[] props = type.GetProperties();
        var sortedProps = new SortedList<DisplayAttribute, PropertyInfo>(props.Length, new FieldSorter());
        object[] atts;
        int assignedOrder = 1000; // anything without pre-assigned order gets a ridiculously high order value. same for duplicates.
        foreach (var prop in props)
        {
            atts = prop.GetCustomAttributes(typeof(DisplayAttribute), true);
            if (atts.Length > 0)
            {
                var att = (DisplayAttribute)atts[0];
                if (!att.GetOrder().HasValue || sortedProps.Keys.Contains(att, new FieldSorter()))
                    att.Order = assignedOrder++;
                sortedProps.Add(att, prop);
            }
        }
        return sortedProps;
    }
}

Это дает вам SortedList, где ключ - это атрибут и значениеэто PropertyInfo.Это было потому, что мне все еще нужно было получить доступ к другим свойствам атрибута.

Пример использования:

        public class Stats 
        {
            [Display(Name = "Changes", Description = "Changed records.", Order = 8)]
            public int RecordsWithChanges { get; set; }
            [Display(Name = "Invalid", Description = "Number of invalid records analyzed.", Order = 4)]
            public int InvalidRecordCount { get; set; }
            [Display(Name = "Valid", Description = "Number of valid records.", Order = 6)]
            public int ValidRecordCount { get; set; }
            [Display(Name = "Cost", Description = "Number of records with a Cost value.", Order = 10)]
            public int RecordsWithCost { get; set; }
            public Stats(int changed, int valid, int invalid, int cost)
            {
                RecordsWithChanges = changed;
                ValidRecordCount = valid;
                InvalidRecordCount = invalid;
                RecordsWithCost = cost;
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                var foo = new Stats(123, 456, 7, 89);
                var fields = FieldSorter.GetSortedFields(foo.GetType());
                foreach (DisplayAttribute att in fields.Keys)
                    Console.WriteLine("{0}: {1} ({2}) == {3}", 
                        att.Order, att.Name, att.Description, fields[att].GetValue(foo, null));
null));

            }
        }

Вывод:

4: Invalid (Number of invalid records analyzed.) -- 7
6: Valid (Number of valid records.) -- 456
8: Changes (Changed records.) -- 123
10: Cost (Number of records with a Cost value.) -- 89
...