使用反射按声明顺序获取属性

发布于 2024-12-29 11:55:07 字数 465 浏览 2 评论 0原文

我需要按照类中声明的顺序使用反射获取所有属性。根据 MSDN,使用 GetProperties() 时不能保证顺序

GetProperties 方法不返回特定属性 顺序,例如字母顺序或声明顺序。

但我读到有一种解决方法,可以通过 MetadataToken 对属性进行排序。所以我的问题是,这安全吗?我在 MSDN 上似乎找不到任何有关它的信息。或者还有其他方法可以解决这个问题吗?

我当前的实现如下所示:

var props = typeof(T)
   .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
   .OrderBy(x => x.MetadataToken);

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

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

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

发布评论

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

评论(11

江南烟雨〆相思醉 2025-01-05 11:55:07

在 .net 4.5 (甚至 .net 4.0在 vs2012 中) 你可以使用带有 [CallerLineNumber] 属性的巧妙技巧,通过反射做得更好,让编译器为你插入顺序到你的属性中:

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

然后使用反射:

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)
{
   //
}

如果您必须处理部分类,您还可以使用[CallerFilePath]对属性进行排序。

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].

琉璃繁缕 2025-01-05 11:55:07

如果您要走属性路线,这是我过去使用过的方法;

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

然后像这样使用它;

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

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

在哪里;

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

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

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

如果您在所有属性上都没有可比属性的类型上运行该方法,该方法显然会呕吐,因此请小心它的使用方式,它应该足以满足要求。

我省略了 Order : Attribute 的定义,因为 Yahia 的 Marc Gravell 帖子链接中有一个很好的示例。

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.

避讳 2025-01-05 11:55:07

根据 MSDN MetadataToken< /code> 在一个模块内是唯一的 - 没有任何说法可以保证任何顺序。

即使它确实按照您想要的方式运行,这也是特定于实现的,并且可能随时更改,恕不另行通知。

请参阅此旧的 MSDN 博客条目

我强烈建议不要依赖此类实现细节 - 请参阅Marc Gravell 的回答

如果您在编译时需要一些东西,您可以查看Roslyn(尽管还处于非常早期的阶段)。

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

囍孤女 2025-01-05 11:55:07

另一种可能性是使用 System.ComponentModel.DataAnnotations.DisplayAttribute Order 属性。
由于它是内置的,因此无需创建新的特定属性。

然后选择像这样的有序属性

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

并且类可以像这样呈现

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

    [Display(Name = "Description", Order = 1)
    public string Label {get; set; }
}

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; }
}
余生一个溪 2025-01-05 11:55:07

我测试过的按 MetadataToken 排序的方法是有效的。

这里的一些用户声称这在某种程度上不是一个好的方法/不可靠,但我还没有看到任何证据 - 也许当给定的方法不起作用时你可以在这里发布一些代码片段?

关于向后兼容性 - 当您现在正在使用 .net 4 / .net 4.5 时 - Microsoft 正在制作 .net 5 或更高版本,因此您几乎可以假设这种排序方法将来不会被破坏。

当然,也许到 2017 年,当你升级到 .net9 时,你会遇到兼容性问题,但到那时微软的人可能会弄清楚“官方排序机制”。回去或破坏东西是没有意义的。

使用额外的属性进行属性排序也需要时间和实现 - 如果 MetadataToken 排序有效,为什么还要烦恼呢?

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 ?

池予 2025-01-05 11:55:07

您可以在 System.Component.DataAnnotations 中使用 DisplayAttribute,而不是自定义属性。无论如何,您的要求必须与显示有关。

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

瀟灑尐姊 2025-01-05 11:55:07

如果您可以强制您的类型具有已知的内存布局,则可以依赖 StructLayout(LayoutKind.Sequential) 然后按内存中的字段偏移量排序。

这样,您就不需要类型中每个字段的任何属性。

但也有一些严重的缺点:

  • 所有字段类型都必须具有内存表示形式(除了固定长度数组或字符串之外,实际上没有其他引用类型)。这包括父类型,即使您只想对子类型的字段进行排序。
  • 可以将其用于包括继承在内的类,但所有父类还需要设置顺序布局。
  • 显然,这不会对属性进行排序,但字段对于 POCO 可能没问题。
[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}");
    }
  }
}

输出:

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

如果您尝试添加任何禁止的字段类型,您将收到 System.ArgumentException:类型“TestRecord”无法封送为非托管结构;无法计算出有意义的大小或偏移量。

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(
quot;{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.

番薯 2025-01-05 11:55:07

我是这样做的:

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




    }

属性声明如下:

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

}

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

}
铁轨上的流浪者 2025-01-05 11:55:07

在上述接受的解决方案的基础上,要获得确切的索引,您可以使用类似

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< /em>,没有错误检查或容错,可以加胡椒和盐调味

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

夜深人未静 2025-01-05 11:55:07

如果您对额外的依赖项感到满意,Marc Gravell 的 Protobuf-Net 可用于执行此操作而不必担心实现反射和缓存等的最佳方法。只需使用 [ProtoMember] 装饰您的字段,然后使用以下命令按数字顺序访问字段:

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

metaData.GetFields();

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();
洋洋洒洒 2025-01-05 11:55:07

即使它是一个非常古老的线程,这是我基于 @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
            )
            );

并且属性是这样的

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

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

使用这样

[AttrOrder(1)]
public string Name { get; set; }

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