簡體   English   中英

使用反射按聲明順序獲取屬性

[英]Get properties in order of declaration using reflection

我需要按照它們在類中聲明的順序使用反射來獲取所有屬性。 根據 MSDN,使用GetProperties()時無法保證順序

GetProperties 方法不按特定順序(如字母順序或聲明順序)返回屬性。

但是我讀到有一種解決方法,可以通過MetadataToken對屬性進行排序。 所以我的問題是,這樣安全嗎? 我似乎在 MSDN 上找不到任何關於它的信息。 或者有沒有其他方法可以解決這個問題?

我當前的實現如下所示:

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

在 .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]對屬性進行額外排序。

如果您要走屬性路線,這是我過去使用的一種方法;

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

根據MSDN MetadataToken在一個模塊內是唯一的 - 沒有什么可以保證任何順序。

即使它確實按照您希望的方式運行,也將是特定於實現的,並且可能隨時更改,恕不另行通知。

請參閱這個舊的MSDN 博客條目

我強烈建議遠離對此類實現細節的任何依賴 - 請參閱Marc Gravell 的這個答案

如果您在編譯時需要一些東西,您可以看看Roslyn (盡管它處於非常早期的階段)。

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

我測試過的 MetadataToken 排序是有效的。

這里的一些用戶聲稱這在某種程度上不是好方法/不可靠,但我還沒有看到任何證據 - 當給定的方法不起作用時,也許你可以在這里發布一些代碼片段?

關於向后兼容性——當你現在正在使用你的 .net 4 / .net 4.5 時——微軟正在制作 .net 5 或更高版本,所以你幾乎可以假設這種排序方法將來不會被破壞。

當然,也許到 2017 年你將升級到 .net9 時,你會遇到兼容性問題,但到那時微軟人可能會弄清楚“官方排序機制”。 回去或破壞東西是沒有意義的。

使用額外的屬性進行屬性排序也需要時間和實施——如果 MetadataToken 排序有效,為什么還要打擾?

您可以在 System.Component.DataAnnotations 中使用 DisplayAttribute,而不是自定義屬性。 無論如何,您的要求必須與顯示有關。

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

如果您對額外的依賴感到滿意,可以使用 Marc Gravell 的Protobuf-Net來執行此操作,而不必擔心實現反射和緩存等的最佳方法。只需使用[ProtoMember]裝飾您的字段,然后以數字形式訪問字段訂購使用:

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

metaData.GetFields();

我是這樣做的:

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

}

在上述接受的解決方案的基礎上,要獲得確切的索引,您可以使用類似這樣的東西

給定

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

注意,沒有錯誤檢查或容錯,可以加胡椒和鹽調味

即使這是一個非常古老的線程,這是我基於@Chris McAtackney 的工作解決方案

        var props = rootType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .OrderBy(p =>
            (
            p.GetCustomAttributes(typeof(AttrOrder), false).Length != 0 ? // if we do have this attribute
            ((p.GetCustomAttributes(typeof(AttrOrder), false)[0]) as AttrOrder).Order
            : int.MaxValue // or just a big value
            )
            );

而且屬性是這樣的

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AttrOrder : Attribute
{
    public int Order { get; }

    public AttrOrder(int order)
    {
        Order = order;
    }
}

像這樣使用

[AttrOrder(1)]
public string Name { get; set; }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM