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

时间:2012-01-30 10:09:48

标签: c# reflection properties getproperties

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

时无法保证订单
  

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

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

我目前的实施情况如下:

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

9 个答案:

答案 0 :(得分:122)

在.net 4.5 (and even .net 4.0 in 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]对属性进行排序。

答案 1 :(得分:12)

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

即使它确实按照您希望的方式运行,也会针对具体实施,并且可能随时更改,恕不另行通知。

查看旧的MSDN blog entry

我强烈建议不要依赖此类实施细节 - 请参阅this answer from Marc Gravell

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

答案 2 :(得分:11)

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

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

如果你在所有属性上都没有类似属性的类型上运行它,那么该方法会barf,所以要小心它是如何使用的,它应该足以满足要求。

我遗漏了Order:Attribute的定义,因为在Yahia链接到Marc Gravell的帖子中有一个很好的样本。

答案 3 :(得分:4)

我测试过MetadataToken排序的工作原理。

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

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

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

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

metaData.GetFields();

答案 6 :(得分: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_; } }

}

答案 7 :(得分: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);

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

答案 8 :(得分:0)

另一种可能性是使用Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0+k3s.1", GitCommit:"0f644650f5d8e9f091629f860b342f221c46f6d7", GitTreeState:"clean", BuildDate:"2020-01-06T23:20:30Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/arm"} Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0+k3s.1", GitCommit:"0f644650f5d8e9f091629f860b342f221c46f6d7", GitTreeState:"clean", BuildDate:"2020-01-06T23:20:30Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/arm"}``` System.ComponentModel.DataAnnotations.DisplayAttribute属性。 由于它是内置的,因此无需创建新的特定属性。

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

Order

班级可以这样呈现

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