Get properties in order of declaration using reflection
Asked Answered
F

11

106

I need to get all the properties using reflection in the order in which they are declared in the class. According to MSDN the order can not be guaranteed when using GetProperties()

The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order.

But I've read that there is a workaround by ordering the properties by the MetadataToken. So my question is, is that safe? I cant seem find any information on MSDN about it. Or is there any other way of solving this problem?

My current implementation looks as follows:

var props = typeof(T)
   .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
   .OrderBy(x => x.MetadataToken);
Firecure answered 30/1, 2012 at 10:9 Comment(7)
Anyway, it is bad idea. Create your own attribute with order value or any other metadata and mark fields of class with that attribute.Pandarus
You could perhaps add a new attribute which contains an int of the order. Then get the properties, get each property's DisplayOrderAttribute and sort by that?Confederate
Out of curiosity, why are you doing this, what are you trying to achieve?Nodarse
I'm writing the content of the objects to a text file. And the output needs to be in a specific order.Firecure
But im probably gonna go with @KirillPolishchuk recommendation on this.Firecure
@Firecure Yet the question is still an interesting one because some parts of framework itself heavily rely on this. For example serialization with Serializable attribute stores members in the order they were defined. At leas Wagner states this in his book "Effective C#"Magus
possible duplicate of C# Get FieldInfos/PropertyInfos in the original order?Anstus
R
187

On .net 4.5 (and even .net 4.0 in vs2012) you can do much better with reflection using clever trick with [CallerLineNumber] attribute, letting compiler insert order into your properties for you:

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class OrderAttribute : Attribute
{
    private readonly int order_;
    public OrderAttribute([CallerLineNumber]int order = 0)
    {
        order_ = order;
    }

    public int Order { get { return order_; } }
}


public class Test
{
    //This sets order_ field to current line number
    [Order]
    public int Property2 { get; set; }

    //This sets order_ field to current line number
    [Order]
    public int Property1 { get; set; }
}

And then use reflection:

var properties = from property in typeof(Test).GetProperties()
                 where Attribute.IsDefined(property, typeof(OrderAttribute))
                 orderby ((OrderAttribute)property
                           .GetCustomAttributes(typeof(OrderAttribute), false)
                           .Single()).Order
                 select property;

foreach (var property in properties)
{
   //
}

If you have to deal with partial classes, you can additionaly sort the properties using [CallerFilePath].

Roof answered 1/8, 2013 at 15:30 Comment(11)
This is very clever indeed! I wonder if it has any counter arguments against it? Does seem to be quite elegant to me actually. I'm using Linq2CSV and I think I'll inherit from CsvColumnAttributeand use this as the default FieldIndex valueLucrece
@Lucrece I suppose the argument against this is that there is an undocumented, positional API that someone could break when refactoring if they didn't understand that the attribute was being used in this fashion. I still think it's pretty elegant, just saying in case someone copy/pastes this and is looking for motivation to leave some comments for the next guy.Catlaina
This is works except for the case where 1 class inherits another class and I need the order of all properties. Is there a trick for that as well?Argyll
I think this will break if the class is declared in partial. Unlike inheritance, the API can not figure it out.Merri
Very clever approach but it will throw null reference exception if some forget to set [Order] attribute. Better to check null before calling. Ex: var properties = from property in typeof(Test).GetProperties() let orderAttribute = (property.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault()==null? new OrderAttribute(0) : property.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault()) as OrderAttribute orderby orderAttribute.Order select property;Pelagi
I also have no properties returned from those in a MetadataClassGerm
Minor note on the "partial classes" comment at the bottom; there is no defined order when it comes to partial classes; in reality, an implementation detail is that they get there in the order in which they are passed to the compiler, but even that isn't defined anywhere as being a hard rule.Turboelectric
@Marc Gravell That's why I suggested that you can sort using CallerFilePath - at least in this case you have defined order. Safer solution could be to just throw if CallerFilePath differs between properties.Roof
Also, this doesn't work with anonymous types - which may be passed to a method in the fashion of a template. See JsonConvert.SerializeAnonymousType for an example of what I mean. It's a useful trick for when you don't want or need to create a DTO, but you can't apply attributes to the properties of an anonymous type.Gower
Thanks for the answer!! I would like to add a link to another answer for allowing to keep properties not tagged with [Order] and adding them by default after or before those tagged with [Order]: @Sergey Berezovskiy #22307189.Complimentary
Clever answer but does not work if used on EF navigation property. Probably because proxy classes are used.Herbert
M
20

If you're going the attribute route, here's a method I've used in the past;

public static IOrderedEnumerable<PropertyInfo> GetSortedProperties<T>()
{
  return typeof(T)
    .GetProperties()
    .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order);
}

Then use it like this;

var test = new TestRecord { A = 1, B = 2, C = 3 };

foreach (var prop in GetSortedProperties<TestRecord>())
{
    Console.WriteLine(prop.GetValue(test, null));
}

Where;

class TestRecord
{
    [Order(1)]
    public int A { get; set; }

    [Order(2)]
    public int B { get; set; }

    [Order(3)]
    public int C { get; set; }
}

The method will barf if you run it on a type without comparable attributes on all of your properties obviously, so be careful how it's used and it should be sufficient for requirement.

I've left out the definition of Order : Attribute as there's a good sample in Yahia's link to Marc Gravell's post.

Maggiemaggio answered 30/1, 2012 at 13:2 Comment(1)
If this is a display requirement, it would be appropriate to use DataAnnotations.DisplayAttribute, which has an Order field.Marcin
B
12

According to MSDN MetadataToken is unique inside one Module - there is nothing saying that it guarantees any order at all.

EVEN if it did behave the way you want it to that would be implementation-specific and could change anytime without notice.

See this old MSDN blog entry.

I would strongly recommend to stay away from any dependency on such implementation details - see this answer from Marc Gravell.

IF you need something at compile time you could take a look at Roslyn (although it is in a very early stage).

Brinson answered 30/1, 2012 at 11:3 Comment(0)
B
9

Another possibility is to use the System.ComponentModel.DataAnnotations.DisplayAttribute Order property. Since it is builtin, there is no need to create a new specific attribute.

Then select ordered properties like this

const int defaultOrder = 10000;
var properties = type.GetProperties().OrderBy(p => p.FirstAttribute<DisplayAttribute>()?.GetOrder() ?? defaultOrder).ToArray();

And class can be presented like this

public class Toto {
    [Display(Name = "Identifier", Order = 2)
    public int Id { get; set; }

    [Display(Name = "Description", Order = 1)
    public string Label {get; set; }
}
Belligerence answered 21/1, 2020 at 15:42 Comment(4)
Any idea why this is getting so little attention versus the "chosen answer" which is literally doing what the built-in DisplayAttribute does for us? This is the better answer as it doesn't require building a brand new custom attribute.Artistic
What is "FirstAttribute"? Doesn't seem standard.Grillo
Also, what is "GetOrder"? Doesn't seem standard.Grillo
@LinusProxy GetOrder is a DisplayAttribute native method.Belligerence
C
4

What I have tested sorting by MetadataToken works.

Some of users here claims this is somehow not good approach / not reliable, but I haven't yet seen any evidence of that one - perhaps you can post some code snipet here when given approach does not work ?

About backwards compatibility - while you're now working on your .net 4 / .net 4.5 - Microsoft is making .net 5 or higher, so you pretty much can assume that this sorting method won't be broken in future.

Of course maybe by 2017 when you will be upgrading to .net9 you will hit compatibility break, but by that time Microsoft guys will probably figure out the "official sort mechanism". It does not makes sense to go back or break things.

Playing with extra attributes for property ordering also takes time and implementation - why to bother if MetadataToken sorting works ?

Cachexia answered 9/3, 2015 at 20:18 Comment(6)
It is 2019 and not even .net-4.9 has been released yet :-p.Franky
Each time .net releases a major version is a great opportunity for them to make changes to things like MetadataToken or the ordering of the return from GetProperties(). Your argument for why you can rely on the ordering is exactly the same as the argument for why you can’t rely on future versions of .net not changing this behavior: every new release is free to change implementation details. Now, .net actually follows the philosophy that “bugs are features”, so they in reality probably never would change the ordering of GetProperties(). It’s just that the API says they are allowed to.Franky
Well I just ported some code from C++/CLI to C#, and the usage of MetadataToken in a comparer class broke all of our XML element ordering. Somebody thought they were clever, and now years later, other developers have to suffer. Thanks a lot buddy!Gould
Which net framework you have used and how did you sort it ?Cachexia
I came back to this now with a clear head, and it seems like C++/CLI orders reflected properties differently, which is why the code started failing, but nevertheless it still shows why this method is not really safe, because now our XML ordering is tied to the language/framework implementation which makes porting/upgrading always a danger.Gould
For those running into this same issue, if there even are any in this world, all I had to do was to flip the comparison operator and suddenly the crappy hack worked with C# models as well. So x.MetadataToken < y.MetadataToken ? -1 : 1;.Gould
N
1

You may use DisplayAttribute in System.Component.DataAnnotations, instead of custom attribute. Your requirement has to do something with display anyway.

Nabokov answered 5/10, 2016 at 16:14 Comment(0)
T
1

If you can enforce your type has a known memory layout, you can rely on StructLayout(LayoutKind.Sequential) then sort by the field offsets in memory.

This way you don't need any attribute on each field in the type.

Some serious drawbacks though:

  • All field types must have a memory representation (practically no other reference types other than fixed-length arrays or strings). This includes parent types, even if you just want to sort the child type's fields.
  • You can use this for classes including inheritance, but all parent classes need to also have sequential layout set.
  • Obviously, this doesn't sort properties but fields might be fine for POCOs.
[StructLayout(LayoutKind.Sequential)]
struct TestStruct
{
  public int x;
  public decimal y;
}

[StructLayout(LayoutKind.Sequential)]
class TestParent
{
  public int Base;
  public TestStruct TestStruct;
}

[StructLayout(LayoutKind.Sequential)]
class TestRecord : TestParent
{
  public bool A;
  public string B;
  public DateTime C;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 42)] // size doesn't matter
  public byte[] D;
}

class Program
{
  static void Main(string[] args)
  {
    var fields = typeof(TestRecord).GetFields()
      .OrderBy(field => Marshal.OffsetOf(field.DeclaringType, field.Name));
    foreach (var field in fields) {
      Console.WriteLine($"{field.Name}: {field.FieldType}");
    }
  }
}

Outputs:

Base: System.Int32
TestStruct: TestStruct
A: System.Boolean
B: System.String
C: System.DateTime
D: System.Byte[]

If you try to add any forbidden field types, you'll get System.ArgumentException: Type 'TestRecord' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

Tuppeny answered 11/11, 2021 at 4:46 Comment(0)
A
0

If you are happy with the extra dependency, Marc Gravell's Protobuf-Net can be used to do this without having to worry about the best way to implement reflection and caching etc. Just decorate your fields using [ProtoMember] and then access the fields in numerical order using:

MetaType metaData = ProtoBuf.Meta.RuntimeTypeModel.Default[typeof(YourTypeName)];

metaData.GetFields();
Azygous answered 14/12, 2012 at 13:8 Comment(0)
P
0

I did it this way:

 internal static IEnumerable<Tuple<int,Type>> TypeHierarchy(this Type type)
    {
        var ct = type;
        var cl = 0;
        while (ct != null)
        {
            yield return new Tuple<int, Type>(cl,ct);
            ct = ct.BaseType;
            cl++;
        }
    }

    internal class PropertyInfoComparer : EqualityComparer<PropertyInfo>
    {
        public override bool Equals(PropertyInfo x, PropertyInfo y)
        {
            var equals= x.Name.Equals(y.Name);
            return equals;
        }

        public override int GetHashCode(PropertyInfo obj)
        {
            return obj.Name.GetHashCode();
        }
    }

    internal static IEnumerable<PropertyInfo> GetRLPMembers(this Type type)
    {

        return type
            .TypeHierarchy()
            .SelectMany(t =>
                t.Item2
                .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(prop => Attribute.IsDefined(prop, typeof(RLPAttribute)))
                .Select(
                    pi=>new Tuple<int,PropertyInfo>(t.Item1,pi)
                )
             )
            .OrderByDescending(t => t.Item1)
            .ThenBy(t => t.Item2.GetCustomAttribute<RLPAttribute>().Order)
            .Select(p=>p.Item2)
            .Distinct(new PropertyInfoComparer());




    }

with the property declared as follows:

  [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class RLPAttribute : Attribute
{
    private readonly int order_;
    public RLPAttribute([CallerLineNumber]int order = 0)
    {
        order_ = order;
    }

    public int Order { get { return order_; } }

}
Publicist answered 11/6, 2018 at 12:58 Comment(1)
How is this different or better than the accepted answer?Tret
T
0

Building on the above accepted solution, to get the exact Index you could use something like this

Given

public class MyClass
{
   [Order] public string String1 { get; set; }
   [Order] public string String2 { get; set; }
   [Order] public string String3 { get; set; }
   [Order] public string String4 { get; set; }   
}

Extensions

public static class Extensions
{

   public static int GetOrder<T,TProp>(this T Class, Expression<Func<T,TProp>> propertySelector)
   {
      var body = (MemberExpression)propertySelector.Body;
      var propertyInfo = (PropertyInfo)body.Member;
      return propertyInfo.Order<T>();
   }

   public static int Order<T>(this PropertyInfo propertyInfo)
   {
      return typeof(T).GetProperties()
                      .Where(property => Attribute.IsDefined(property, typeof(OrderAttribute)))
                      .OrderBy(property => property.GetCustomAttributes<OrderAttribute>().Single().Order)
                      .ToList()
                      .IndexOf(propertyInfo);
   }
}

Usage

var myClass = new MyClass();
var index = myClass.GetOrder(c => c.String2);

Note, there is no error checking or fault tolerance, you can add pepper and salt to taste

Toney answered 24/12, 2018 at 0:0 Comment(0)
L
0

Even it's a very old thread, here is my working solution based on @Chris McAtackney

        var props = rootType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .OrderBy(p =>
            (
            p.GetCustomAttributes(typeof(AttrOrder), false).Length != 0 ? // if we do have this attribute
            ((p.GetCustomAttributes(typeof(AttrOrder), false)[0]) as AttrOrder).Order
            : int.MaxValue // or just a big value
            )
            );

And the Attribute is like this

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

    public AttrOrder(int order)
    {
        Order = order;
    }
}

Use like this

[AttrOrder(1)]
public string Name { get; set; }
Lysippus answered 12/7, 2022 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.