`Type.GetProperties` property order

后端 未结 5 1917
一生所求
一生所求 2020-12-09 16:13

Short Version

The MSDN documentation for Type.GetProperties states that the collection it returns is not guaranteed to be in alphabetical or declara

相关标签:
5条回答
  • 2020-12-09 16:29

    The order simply isn't guaranteed; whatever happens.... Happens.

    Obvious cases where it could change:

    • anything that implements ICustomTypeDescriptor
    • anything with a TypeDescriptionProvider

    But a more subtle case: partial classes. If a class is split over multiple files, the order of their usage is not defined at all. See Is the "textual order" across partial classes formally defined?

    Of course, it isn't defined even for a single (non-partial) definition ;p

    But imagine

    File 1

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

    File 2

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

    There is no formal declaration order here between A and B. See the linked post to see how it tends to happen, though.


    Re your edit; the best approach there is to specify the marshal info separately; a common approach would be to use a custom attribute that takes a numeric order, and decorate the members with that. You can then order based on this number. protobuf-net does something very similar, and frankly I'd suggest using an existing serialization library here:

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

    Where "n" is an integer. In the case of protobuf-net specifically, there is also an API to specify these numbers separately, which is useful when the type is not under your direct control.

    0 讨论(0)
  • 2020-12-09 16:35

    For what it's worth, sorting by MetadataToken seemed to work for me.

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

    Original Article (broken link, just listed here for attribution): http://www.sebastienmahe.com/v3/seb.blog/2010/03/08/c-reflection-getproperties-kept-in-declaration-order/

    0 讨论(0)
  • 2020-12-09 16:40

    I use custom attributes to add the necessary metadata myself (it's used with a REST like service which consumes and returns CRLF delimited Key=Value pairs.

    First, a custom attribute:

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

    Then, decorate your classes:

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

    A handy method for converting a PropertyInfo into a sortable 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;
        }
    

    Even better, write is as an extension:

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

    Finally you can now query your Type object with:

            var props = from p in type.GetProperties()
                        where p.CanWrite
                        orderby p.PropertyOrder() ascending
                        select p;
    
    0 讨论(0)
  • 2020-12-09 16:42

    Relying on an implementation detail that is explicitly documented as being not guaranteed is a recipe for disaster.

    The 'recommended approach' would vary depending on what you want to do with these properties once you have them. Just displaying them on the screen? MSDN docs group by member type (property, field, function) and then alphabetize within the groups.

    If your message format relies on the order of the fields, then you'd need to either:

    1. Specify the expected order in some sort of message definition. Google protocol buffers works this way if I recall- the message definition is compiled in that case from a .proto file into a code file for use in whatever language you happen to be working with.

    2. Rely on an order that can be independently generated, e.g. alphabetical order.

    0 讨论(0)
  • 2020-12-09 16:44

    1:

    I've spent the last day troubleshooting a problem in an MVC 3 project, and it all came down to this particular problem. It basically relied on the property order being the same throughout the session, but on some occations a few of the properties switched places, messing up the site.

    First the code called Type.GetProperties() to define column names in a dynamic jqGrid table, something that in this case occurs once per page_load. Subsequent times the Type.GetProperties() method was called was to populate the actual data for the table, and in some rare instances the properties switched places and messed up the presentation completely. In some instances other properties that the site relied upon for a hierarchical subgrid got switched, i.e. you could no longer see the sub data because the ID column contained erroneous data. In other words: yes, this can definitely happen. Beware.

    2:

    If you need consistent order throughout the system session but not nessecarily exactly the same order for all sessions the workaround is dead simple: store the PropertyInfo[] array you get from Type.GetProperties() as a value in the webcache or in a dictionary with the type (or typename) as the cache/dictionary key. Subsequently, whenever you're about to do a Type.GetProperties(), instead substitute it for HttpRuntime.Cache.Get(Type/Typename) or Dictionary.TryGetValue(Type/Typename, out PropertyInfo[]). In this way you'll be guaranteed to always get the order you encountered the first time.

    If you always need the same order (i.e. for all system sessions) I suggest you combine the above approach with some type of configuration mechanism, i.e. specify the order in the web.config/app.config, sort the PropertyInfo[] array you get from Type.GetProperties() according to the specified order, and then store it in cache/static dictionary.

    0 讨论(0)
提交回复
热议问题