`Type.GetProperties`属性顺序

时间:2012-01-15 18:33:44

标签: c# reflection

短版

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. 如果不推荐依赖于订单,那么 推荐的方法是什么?
  3. 一个看似显而易见的解决方案是定义一个自定义属性,该属性指示属性出现的顺序(类似于Order属性上的DataMember属性) 。类似的东西:

    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的返回集合来工作,但由于文档声明不推荐我,我想了解为什么以及我有什么其他选项?

5 个答案:

答案 0 :(得分:14)

订单根本无法保证;无论发生什么......发生。

可能发生变化的明显案例:

  • 任何实现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可以单独指定这些数字,这在类型不在您的直接控制之下时非常有用。

答案 1 :(得分:4)

依赖于明确记录为无法保证的实施细节是灾难的一种方法。

“推荐方法”取决于您拥有这些属性后要对这些属性执行的操作。只是在屏幕上显示它们? MSDN文档按成员类型(属性,字段,函数)分组,然后在组内按字母顺序排列。

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

  1. 在某种消息定义中指定预期的顺序。如果我记得的话,谷歌protocol buffers会以这种方式工作 - 在这种情况下,邮件定义会从.proto文件编译成代码文件,以便用于您正在使用的任何语言。

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

答案 2 :(得分:4)

我使用自定义属性自己添加必要的元数据(它与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;

答案 3 :(得分:4)

对于它的价值,按MetadataToken排序似乎对我有用。

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

原创文章: http://www.sebastienmahe.com/v3/seb.blog/2010/03/08/c-reflection-getproperties-kept-in-declaration-order/

答案 4 :(得分:3)

<强> 1

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

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

<强> 2

如果您需要在整个系统会话中保持一致的顺序,但并非所有会话的顺序完全相同,则解决方法是死简单:存储从{{1}获得的PropertyInfo[]数组}作为webcache中的值或字典中的类型(或typename)作为缓存/字典键。随后,无论何时您要执行Type.GetProperties(),请将其替换为Type.GetProperties()HttpRuntime.Cache.Get(Type/Typename)。通过这种方式,您可以保证始终获得您第一次遇到的订单。

如果您总是需要相同的订单(即所有系统会话),我建议您将上述方法与某种类型的配置机制相结合,即在web.config / app.config中指定顺序,对{{1您根据指定的顺序从Dictionary.TryGetValue(Type/Typename, out PropertyInfo[])获取数组,然后将其存储在缓存/静态字典中。