保证列表中属性的顺序与它们在代码文件中出现的顺序相匹配

发布于 2024-11-30 10:06:09 字数 1490 浏览 0 评论 0原文

我有一个接口,它定义了返回 IList 的方法:

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 的优点,如果有任何可以帮助的东西,尽管它会很有趣。)

I have an interface that defines a method for returning an IList<PropertyInfo> :

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

.
.
It is implemented in various (dissimilar) classes in the following manner:

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
}

Please note the attributes marking a couple of the properties, as these are the properties that will get added to the list. This IList will then be used elsewhere during some file write operations.

It is important to me that they are ordered in the list in the order they appear in the code file.

However, MSDN states:

The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.

So, what is the best way to ensure each PropertyInfo gets added in the order I would like to to be?

(I am also using .NET2.0, so I can't use any Linq goodness, should there be any that would help, although it would be interesting to see.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

柳絮泡泡 2024-12-07 10:06:09

将有关排序的信息添加到属性中,然后您可以使用它来确保排序,例如:

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

Add information to the attribute about the ordering, you can then use this to ensure the ordering, e.g.:

[Writeable(Order = 1)]

So for the following attribute:

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

You can get an ordered selection of the properties as follows:

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 For production code I would recommend caching the property information (per type for example) as this will be relatively slow if carried out for each instance.

Update - Caching

With some example caching of property lookup and ordering:

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

Update - No Linq

You can replace the Linq section with the following code to order the properties and add them to the cache:

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

Update - Full Linq

And using a fully Linq solution:

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;
    }
}
木緿 2024-12-07 10:06:09

不久前,当我遇到同样的问题时,我编写了一个辅助类来根据属性的 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:无效(分析的无效记录数。) -- 7
6:有效(有效记录数)-- 456
8:更改(更改记录。)-- 123
10:成本(具有成本值的记录数。)-- 89

A while ago when I had the same problem I wrote a helper class to sort the properties based on the Order property of the attribute. I used the built-in DisplayAttribute but you can just add an Order property to any attribute you write.

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

This gives you a SortedList where the key is the attribute and the value is the PropertyInfo. This was because I still needed to access other properties of the attribute.

Example usage:

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

            }
        }

Output:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文