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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9062235/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 05:45:28  来源:igfitidea点击:

Get properties in order of declaration using reflection

c#reflectionpropertiesgetproperties

提问by Magnus

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

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

The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order.

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

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?

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

My current implementation looks as follows:

我当前的实现如下所示:

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

采纳答案by ghord

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:

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

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

如果您必须处理部分类,您还可以使用[CallerFilePath].

回答by Yahia

According to MSDNMetadataTokenis unique inside one Module - there is nothing saying that it guarantees any order at all.

根据MSDNMetadataToken在一个模块中是独一无二的 - 没有什么说它可以保证任何顺序。

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.

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

I would strongly recommend to stay away from any dependency on such implementation details - see this answer from Marc Gravell.

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

IF you need something at compile time you could take a look at Roslyn(although it is in a very early stage).

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

回答by Christopher McAtackney

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.

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

回答by Tom Makin

If you are happy with the extra dependency, Marc Gravell's Protobuf-Netcan 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:

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

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

metaData.GetFields();

回答by TarmoPikaro

What I have tested sorting by MetadataToken works.

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

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.

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

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.

当然,也许到 2017 年,当您升级到 .net9 时,您会遇到兼容性问题,但到那时,Microsoft 人员可能会弄清楚“官方排序机制”。返回或破坏事物是没有意义的。

Playing with extra attributes for property ordering also takes time and implementation - why to bother if MetadataToken sorting works ?

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

回答by Panos Roditakis

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

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

回答by Sentinel

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

}

回答by TheGeneral

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

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

回答by labilbe

Another possibility is to use the System.ComponentModel.DataAnnotations.DisplayAttributeOrderproperty. Since it is builtin, there is no need to create a new specific attribute.

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

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