[英]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)
然后按內存中的字段偏移量進行排序。
這樣,您不需要類型中每個字段的任何屬性。
但是有一些嚴重的缺點:
[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.