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


81

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

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

但是我读过,有一种解决方法,即按排序属性MetadataToken。所以我的问题是,这样安全吗?我似乎找不到有关MSDN的任何信息。还是有其他解决方法?

我当前的实现如下所示:

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

12
无论如何,这是一个坏主意。使用订单值或任何其他元数据创建您自己的属性,并使用该属性标记类的字段。
Kirill Polishchuk 2012年

1
您也许可以添加一个包含订单int的新属性。然后获取属性,获取每个属性的DisplayOrderAttribute并按此排序?
BlueChippy 2012年

1
出于好奇,您为什么要这样做,您想达到什么目的?
Sam Greenhalgh'1

6
@Magnus然而,这个问题仍然是一个有趣的问题,因为框架本身的某些部分严重依赖于此。例如,使用Serializable属性进行序列化将按定义成员的顺序存储成员。沃格纳(Wagner)在他的书“有效的C#”中指出了这一点
帕维尔·沃罗宁

Answers:


143

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


2
确实是非常聪明!我想知道它是否对此有任何反对意见?实际上对我来说似乎很优雅。我使用的是Linq2CSV,我想我将继承CsvColumnAttribute并使用它作为默认FieldIndex
julealgon 2013年

2
@julealgon我想反对的观点是,有一个未公开的位置API,如果有人不了解该属性是以这种方式使用的,则有人在重构时可能会中断。我仍然认为这很优雅,只是说说以防有人复制/粘贴此内容,并正在寻找动力给下一个家伙留下一些评论。
乔尔·B

2
除1类继承另一个类并且我需要所有属性的顺序的情况外,这是可行的。还有一个窍门吗?
保罗·巴克斯特

我认为,如果该类是部分声明的,则将中断。与继承不同,API无法解决它。
吴振伟

1
非常聪明的方法,但是如果有人忘记设置[Order]属性,它将抛出null引用异常。最好在调用前检查null。例如:var properties =来自typeof(Test).GetProperties()中的属性let orderAttribute =(property.GetCustomAttributes(typeof(OrderAttribute),false).SingleOrDefault()== null?new OrderAttribute(0):property.GetCustomAttributes(typeof (OrderAttribute),false).SingleOrDefault())作为OrderAttribute orderby orderAttribute.Order select属性;
索潘·麦迪

15

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

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


2
如果这是显示要求,则最好使用具有Order字段的DataAnnotations.DisplayAttribute
耶夫,2013年

12

根据MSDN MetadataToken,在一个模块中它是唯一的-没什么可说的是它可以保证任何订单。

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

请参阅此旧的MSDN博客条目

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

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


5

我测试过的按MetadataToken排序的作品有效。

这里的一些用户声称这不是一种好的方法/不可靠,但是我还没有看到任何证据—当给定的方法不起作用时,您可以在此处发布一些代码片段?

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

当然,也许到2017年,当您将要升级到.net9时,您将遇到兼容性中断,但是到那时,微软公司的人们可能会发现“官方排序机制”。退回或破坏事物没有意义。

使用额外的属性进行属性排序也需要时间和实现-为什么要烦扰MetadataToken排序工作呢?


1
现在是2019年,甚至.net-4.9尚未发布:-p。
binki

每次.net发布主要版本时,这都是他们进行MetadataToken或from的返回顺序之类的更改的绝佳机会GetProperties()。关于为什么可以依赖顺序的论点与为什么不能依赖.net未来版本不改变这种行为的论点完全相同:每个新版本都可以自由更改实现细节。现在,.net实际上遵循“ bug是功能”的理念,因此实际上它们可能永远不会改变。的顺序GetProperties()。API只是说允许它们这样做。
binki

1

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


0

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

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

metaData.GetFields();

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

}

与接受的答案有何不同或更好?
Ian Kemp

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

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


0

另一种可能性是使用该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; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.