【问题标题】:`Type.GetProperties` property order`Type.GetProperties` 属性顺序
【发布时间】:2012-02-10 22:28:23
【问题描述】:

短版

Type.GetProperties 的 MSDN 文档指出,它返回的集合不能保证按字母顺序或声明顺序,尽管运行一个简单的测试表明它通常按声明顺序返回。您是否知道某些特定情况并非如此?除此之外,建议的替代方案是什么?

详细版本

我意识到 Type.GetProperties 状态的 MSDN 文档:

GetProperties 方法不返回特定的属性 顺序,例如字母顺序或声明顺序。您的代码不得 取决于返回属性的顺序,因为 顺序不同。

因此无法保证该方法返回的集合将以任何特定方式排序。根据一些测试,我发现返回的属性按照它们在类型中定义的顺序出现。

例子:

class Simple
{
    public int FieldB { get; set; }
    public string FieldA { get; set; }
    public byte FieldC { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Simple Properties:");
        foreach (var propInfo in typeof(Simple).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);
    }
}

输出:

Simple Properties:
        FieldB
        FieldA
        FieldC

这种情况略有不同的一种情况是,当所讨论的类型的父级也具有属性时:

class Parent
{
    public int ParentFieldB { get; set; }
    public string ParentFieldA { get; set; }
    public byte ParentFieldC { get; set; }
}

class Child : Parent
{
    public int ChildFieldB { get; set; }
    public string ChildFieldA { get; set; }
    public byte ChildFieldC { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Parent Properties:");
        foreach (var propInfo in typeof(Parent).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);

        Console.WriteLine("Child Properties:");
        foreach (var propInfo in typeof(Child).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);

    }
}

输出:

Parent Properties:
        ParentFieldB
        ParentFieldA
        ParentFieldC
Child Properties:
        ChildFieldB
        ChildFieldA
        ChildFieldC
        ParentFieldB
        ParentFieldA
        ParentFieldC

这意味着GetProperties 方法在发现属性时会自下而上地沿着继承链向上走。没关系,可以这样处理。

问题:

  1. 是否存在我错过的描述行为会有所不同的特定情况?
  2. 如果不推荐根据顺序,那么推荐的方法是什么

一个看似显而易见的解决方案是定义一个自定义属性,该属性指示属性应该出现的顺序(类似于DataMember 属性上的Order 属性)。比如:

public class PropOrderAttribute : Attribute
{
    public int SeqNbr { get; set; }
}

然后实现如:

class Simple
{
    [PropOrder(SeqNbr = 0)]
    public int FieldB { get; set; }
    [PropOrder(SeqNbr = 1)]
    public string FieldA { get; set; }
    [PropOrder(SeqNbr = 2)]
    public byte FieldC { get; set; }
}

但正如许多人所发现的,如果您的类型有 100 个属性并且您需要在前 2 个属性之间添加一个,这将成为一个严重的维护问题。

更新

此处显示的示例仅用于演示目的。在我的特定场景中,我使用类定义消息格式,然后遍历类的属性并获取它们的属性以查看应如何解组消息中的特定字段。消息中字段的顺序很重要,因此我的类中的属性顺序也很重要。

它目前仅通过迭代来自 GetProperties 的返回集合来工作,但由于文档指出不建议这样做,我希望了解为什么以及我还有什么其他选择?

【问题讨论】:

  • 你想达到什么目的?为什么需要订单?或许还有其他方法
  • 基本上,文档说“不要依赖订单”,但在当前的实现中,它可能会有一个一致的订单。文档是准确的,“不要依赖订单”并不意味着“它会有所不同”。这意味着“它可能会有所不同”。 Q1:你不能假设它会像你再次描述的那样。 Q2:不要依赖订单。如果您需要有序列表,请自行订购。
  • 1.您完全不应该依赖声明顺序 - 这是肯定的,因为它取决于运行时的实现(它甚至可以从一个版本更改为另一个版本)。
  • 2. 为什么不按字母顺序对属性进行排序?还是您需要其他排序顺序?
  • @KierenJohnstone - 我已经根据问题的上下文更新了问题。我有什么选择?

标签: c# reflection


【解决方案1】:

订单根本无法保证;不管发生什么……发生。

可能发生变化的明显情况:

  • 任何实现 ICustomTypeDescriptor 的东西
  • 任何带有 TypeDescriptionProvider 的东西

还有一个更微妙的例子:部分类。如果一个类被拆分为多个文件,则根本没有定义它们的使用顺序。见Is the "textual order" across partial classes formally defined?

当然,即使是单个(非部分)定义也没有定义;p

想象一下

文件 1

partial class Foo {
     public int A {get;set;}
}

文件 2

partial class Foo {
    public int B {get;set:}
}

A 和 B 之间没有正式的声明顺序。不过,请参阅链接的帖子了解它倾向于是如何发生的。


重新编辑;最好的方法是单独指定元帅信息;一种常见的方法是使用采用数字顺序的自定义属性,并用它来装饰成员。然后,您可以根据此编号订购。 protobuf-net 做了一些非常相似的事情,坦率地说,我建议在这里使用现有的序列化库:

[ProtoMember(n)]
public int Foo {get;set;}

其中“n”是一个整数。具体到 protobuf-net 的情况下,还有一个 API 可以分别指定这些数字,这在类型不受您直接控制时很有用。

【讨论】:

  • @M.Bancock 有什么选择?它明确表示:不要依赖顺序
  • 我更新了我的问题,详细说明了我想要实现的目标。
  • 感谢您的澄清。
  • ProtoMember(n) 的想法很好,并且该想法有一个演变,它使用反射根据行号自动设置订单号,在this answer 中有解释。
【解决方案2】:

不管怎样,按 MetadataToken 排序似乎对我有用。

GetType().GetProperties().OrderBy(x => x.MetadataToken)

原始文章(断开的链接,仅在此处列出以供署名): http://www.sebastienmahe.com/v3/seb.blog/2010/03/08/c-reflection-getproperties-kept-in-declaration-order/

【讨论】:

    【解决方案3】:

    我自己使用自定义属性来添加必要的元数据(它与类似 REST 的服务一起使用,该服务使用并返回 CRLF 分隔的 Key=Value 对。

    一、自定义属性:

    class ParameterOrderAttribute : Attribute
    {
        public int Order { get; private set; }
        public ParameterOrderAttribute(int order)
        {
            Order = order;
        }
    }
    

    然后,装饰你的班级:

    class Response : Message
    {
        [ParameterOrder(0)]
        public int Code { get; set; }
    }
    
    class RegionsResponse : Response 
    {
        [ParameterOrder(1)]
        public string Regions { get; set; }
    }
    
    class HousesResponse : Response
    {
        public string Houses { get; set; }
    }
    

    一种将 PropertyInfo 转换为可排序 int 的便捷方法:

        private int PropertyOrder(PropertyInfo propInfo)
        {
            int output;
            var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault();
            output = orderAttr != null ? orderAttr.Order : Int32.MaxValue;
            return output;
        }
    

    更好的是,写是作为扩展:

    static class PropertyInfoExtensions
    {
        private static int PropertyOrder(this PropertyInfo propInfo)
        {
            int output;
            var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault();
            output = orderAttr != null ? orderAttr.Order : Int32.MaxValue;
            return output;
        }
    }
    

    最后,您现在可以使用以下方法查询您的 Type 对象:

            var props = from p in type.GetProperties()
                        where p.CanWrite
                        orderby p.PropertyOrder() ascending
                        select p;
    

    【讨论】:

    • 非常好,完整的答案 :) 希望这被标记为下一个人的答案...
    • 谢谢!再读一遍,现在没时间编辑它,但GetOrder() 将为 PropertyInfo 提供一个非常好的扩展方法。那么它将是orderby p.Order() ascending,您不必担心范围。
    【解决方案4】:

    依赖明确记录为不保证的实现细节是灾难的根源。

    “推荐的方法”会有所不同,具体取决于您在拥有这些属性后要如何处理它们。只是在屏幕上显示它们? MSDN 文档按成员类型(属性、字段、函数)分组,然后在组内按字母顺序排列。

    如果您的消息格式依赖于字段的顺序,那么您需要:

    1. 在某种消息定义中指定预期的顺序。如果我记得的话,Google protocol buffers 就是这样工作的——在这种情况下,消息定义会从 .proto 文件编译成代码文件,以便在您使用的任何语言中使用。

    2. 依赖可以独立生成的订单,例如按字母顺序。

    【讨论】:

    • 我更新了我的问题,详细说明了为什么财产顺序在我的情况下很重要。
    【解决方案5】:

    1:

    我花了最后一天对 MVC 3 项目中的一个问题进行故障排除,这一切都归结为这个特殊问题。它基本上依赖于整个会话中的属性顺序是相同的,但是在某些情况下,一些属性会交换位置,从而弄乱了网站。

    首先调用Type.GetProperties() 的代码在动态jqGrid 表中定义列名,在这种情况下,每个page_load 发生一次。随后调用Type.GetProperties() 方法是为了填充表的实际数据,并且在极少数情况下,属性会切换位置并完全弄乱表示。在某些情况下,站点依赖于分层子网格的其他属性被切换,即您无法再看到子数据,因为 ID 列包含错误数据。换句话说:是的,这肯定会发生。小心。

    2:

    如果您需要在整个系统会话中保持一致的顺序,但并非所有会话的顺序都必须完全相同,则解决方法非常简单:将从Type.GetProperties() 获得的PropertyInfo[] 数组存储为一个值在网络缓存或以类型(或类型名)作为缓存/字典键的字典中。随后,每当您要执行Type.GetProperties() 时,请将其替换为HttpRuntime.Cache.Get(Type/Typename)Dictionary.TryGetValue(Type/Typename, out PropertyInfo[])。这样你就可以保证总是得到你第一次遇到的订单。

    如果你总是需要相同的顺序(即所有系统会话)我建议你将上述方法与某种类型的配置机制结合起来,即在web.config/app.config中指定顺序,排序PropertyInfo[]Type.GetProperties()得到的数组,按照指定的顺序存储到缓存/静态字典中。

    【讨论】:

    • 我发现任何对 GetProperty("xxx") 的调用都会导致属性 xxx 在 GetProperties() 返回的列表中首先返回
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多