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

C# 使用反射按声明顺序获取属性,c#,reflection,properties,getproperties,C#,Reflection,Properties,Getproperties,我需要按照类中声明的顺序使用反射获取所有属性。根据MSDN,使用GetProperties() GetProperties方法不返回特定类型中的属性 顺序,如字母顺序或声明顺序 但是我已经读到了一个变通方法,通过MetadataToken对属性进行排序。所以我的问题是,这样安全吗?我似乎在MSDN上找不到关于它的任何信息。或者有没有其他办法解决这个问题 我当前的实现如下所示: var props = typeof(T) .GetProperties(BindingFlags.Instanc

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

GetProperties方法不返回特定类型中的属性 顺序,如字母顺序或声明顺序

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

我当前的实现如下所示:

var props = typeof(T)
   .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
   .OrderBy(x => x.MetadataToken);
  [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_; } }

}
根据
MetadataToken
的说法,它在一个模块中是唯一的-没有任何迹象表明它能保证任何订单

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

看看这个老家伙

我强烈建议不要依赖这些实现细节-请参阅


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

如果您选择属性路径,这里有一个我过去使用过的方法

public static IOrderedEnumerable<PropertyInfo> GetSortedProperties<T>()
{
  return typeof(T)
    .GetProperties()
    .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order);
}
如果在所有属性上都没有可比较属性的类型上运行该方法,则该方法会呕吐,所以请注意如何使用它,它应该足以满足需求


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

如果您对额外的依赖性感到满意,可以使用Marc Gravell来实现这一点,而不必担心实现反射和缓存等的最佳方式。只需使用
[ProtoMember]来装饰您的字段即可
然后使用以下命令按数字顺序访问字段:

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

metaData.GetFields();
在.net 4.5上,您可以通过使用具有
[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)
{
   //
}

如果必须处理分部类,可以使用我测试过的MetadataToken排序方法对属性进行额外排序

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

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

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


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

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

我是这样做的:

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




    }

在上述公认的解决方案的基础上,要获得准确的索引,您可以使用类似这样的方法

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; }
}
给定的

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

注意,没有错误检查或容错,您可以添加胡椒粉和盐来品尝

另一种可能是使用
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; }
}

无论如何,这是个坏主意。使用order值或任何其他元数据创建您自己的属性,并用该属性标记类的字段。您可以添加一个包含order int的新属性。然后获取属性,获取每个属性的DisplayOrderAttribute并按其排序?出于好奇,你为什么要这样做,你想实现什么?@Magnus然而这个问题仍然很有趣,因为框架本身的某些部分严重依赖于此。例如,具有可序列化属性的序列化将按定义的顺序存储成员。至少瓦格纳在他的书《有效的C#》中指出了这一点。如果这是一个显示要求,那么使用它是合适的,它有一个订单字段。这确实非常聪明!我不知道它是否有反对的理由?对我来说确实很优雅。我使用的是Linq2CSV,我想我将继承自
CsvColumnAttribute
,并将其用作默认的
FieldIndex
value@julealgon我想反对这一点的理由是,有一个没有文件记录的,在重构时,如果不了解该属性是以这种方式使用的,则可能会破坏的位置API。我仍然认为这是非常优雅的,只是说万一有人复制/粘贴了它,并希望为下一个家伙留下一些评论。这是可行的,除了一个类继承另一个类的情况,我需要所有属性的顺序。这也有什么诀窍吗?我认为如果类是部分声明的,那么它就会崩溃。与继承不同的是,API无法理解它。这是一种非常聪明的方法,但如果有人忘记设置[Order]属性,它将抛出空引用异常。最好在调用之前检查null。例如:var properties=from typeof(Test)中的属性。GetProperties()让orderAttribute=(property.GetCustomAttributes(typeof(orderAttribute),false)。SingleOrDefault()==null?新orderAttribute(0):property.GetCustomAttributes(typeof(orderAttribute),false)。SingleOrDefault()作为OrderAttribute orderby OrderAttribute.Order选择属性;它是
public class Toto {
    [Display(Name = "Identifier", Order = 2)
    public int Id { get; set; }

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