96

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

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

但我读过有一种解决方法,即按MetadataToken. 所以我的问题是,这样安全吗?我似乎在 MSDN 上找不到任何关于它的信息。或者有没有其他方法可以解决这个问题?

我当前的实现如下所示:

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

10 回答 10

171

在 .net 4.5 (甚至是 vs2012 中的 .net 4.0)上,您可以使用带有属性的巧妙技巧来更好地利用反射[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].

于 2013-08-01T15:30:59.860 回答
17

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

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 帖子的链接中有一个很好的示例。

于 2012-01-30T13:02:12.133 回答
12

根据MSDN MetadataToken在一个模块内是独一无二的 - 没有什么可以保证任何订单。

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

请参阅这个旧的MSDN 博客条目

我强烈建议远离对此类实现细节的任何依赖 - 请参阅Marc Gravell 的这个答案

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

于 2012-01-30T11:03:36.593 回答
5

另一种可能性是使用该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; }
}
于 2020-01-21T15:42:31.047 回答
4

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

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

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

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

使用额外的属性进行属性排序也需要时间和实施——如果 MetadataToken 排序有效,为什么还要打扰?

于 2015-03-09T20:18:57.787 回答
1

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

于 2016-10-05T16:14:30.220 回答
0

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

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

metaData.GetFields();
于 2012-12-14T13:08:46.393 回答
0

我是这样做的:

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

}
于 2018-06-11T12:58:58.290 回答
0

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

给定

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

扩展

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

用法

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

注意,没有错误检查或容错,可以加胡椒和盐调味

于 2018-12-24T00:00:30.813 回答
0

如果您可以强制您的类型具有已知的内存布局,则可以依靠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: Type 'TestRecord' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

于 2021-11-11T04:46:54.250 回答