Muszę pobrać wszystkie właściwości za pomocą odbicia w kolejności, w jakiej są zadeklarowane w klasie. Według MSDN nie można zagwarantować zamówienia podczas korzystaniaGetProperties()
Metoda GetProperties nie zwraca właściwości w określonej kolejności, takiej jak kolejność alfabetyczna lub deklaracja.
Ale przeczytałem, że istnieje obejście polegające na porządkowaniu właściwości według MetadataToken
. Więc moje pytanie brzmi: czy to jest bezpieczne? Nie mogę znaleźć żadnych informacji na ten temat w witrynie MSDN. Czy jest jakiś inny sposób rozwiązania tego problemu?
Moja obecna realizacja wygląda następująco:
var props = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(x => x.MetadataToken);
c#
reflection
properties
getproperties
Magnus
źródło
źródło
Odpowiedzi:
Na .net 4.5 (a nawet .net 4.0 w vs2012) możesz zrobić dużo lepiej z odbiciem używając sprytnej sztuczki z
[CallerLineNumber]
atrybutem, pozwalając kompilatorowi wstawić porządek do twoich właściwości za Ciebie:[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; } }
A potem użyj refleksji:
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) { // }
Jeśli masz do czynienia z klasami częściowymi, możesz dodatkowo posortować właściwości za pomocą
[CallerFilePath]
.źródło
CsvColumnAttribute
iFieldIndex
Jeśli wybierasz trasę atrybutów, oto metoda, której używałem w przeszłości;
public static IOrderedEnumerable<PropertyInfo> GetSortedProperties<T>() { return typeof(T) .GetProperties() .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order); }
Następnie użyj tego w ten sposób;
var test = new TestRecord { A = 1, B = 2, C = 3 }; foreach (var prop in GetSortedProperties<TestRecord>()) { Console.WriteLine(prop.GetValue(test, null)); }
Gdzie;
class TestRecord { [Order(1)] public int A { get; set; } [Order(2)] public int B { get; set; } [Order(3)] public int C { get; set; } }
Metoda będzie barf, jeśli uruchomisz ją na typie bez porównywalnych atrybutów dla wszystkich twoich właściwości, więc uważaj, jak jest używana i powinna wystarczyć do wymagania.
Pominąłem definicję Order: Attribute, ponieważ w linku Yahia do posta Marca Gravella znajduje się dobry przykład.
źródło
Według MSDN
MetadataToken
jest wyjątkowy wewnątrz jednego modułu - nic nie mówi, że gwarantuje w ogóle jakiekolwiek zamówienie.NAWET gdyby zachowywał się tak, jak chcesz, byłoby to zależne od implementacji i mogłoby się zmienić w dowolnym momencie bez powiadomienia.
Zobacz ten stary wpis w blogu MSDN .
Zdecydowanie polecam trzymać się z daleka od wszelkich zależności od takich szczegółów implementacji - zobacz tę odpowiedź od Marca Gravella .
JEŚLI potrzebujesz czegoś w czasie kompilacji, możesz rzucić okiem na Roslyn (chociaż jest na bardzo wczesnym etapie).
źródło
To, co przetestowałem, sortowanie według MetadataToken działa.
Niektórzy użytkownicy tutaj twierdzą, że nie jest to dobre podejście / niewiarygodne, ale nie widziałem jeszcze żadnych dowodów na to - może możesz zamieścić tutaj fragment kodu, gdy dane podejście nie działa?
O kompatybilności wstecznej - gdy teraz pracujesz nad .net 4 / .net 4.5 - Microsoft tworzy .net 5 lub nowszy, więc możesz założyć, że ta metoda sortowania nie zostanie zepsuta w przyszłości.
Oczywiście, być może do 2017 r., Kiedy będziesz aktualizować do .net9, dojdzie do zerwania kompatybilności, ale do tego czasu pracownicy Microsoft prawdopodobnie wymyślą „oficjalny mechanizm sortowania”. Nie ma sensu cofać się lub przerywać.
Granie z dodatkowymi atrybutami do porządkowania właściwości również wymaga czasu i implementacji - po co zawracać sobie głowę, czy sortowanie MetadataToken działa?
źródło
GetProperties()
. Twój argument przemawiający za tym, dlaczego możesz polegać na kolejności, jest dokładnie taki sam, jak argument, dlaczego nie możesz polegać na przyszłych wersjach .net, które nie zmieniają tego zachowania: każda nowa wersja może zmieniać szczegóły implementacji. Teraz .net faktycznie wyznaje filozofię, według której „błędy to cechy”, więc w rzeczywistości prawdopodobnie nigdy nie zmieniłyby kolejnościGetProperties()
. Po prostu API mówi, że mogą.Możesz użyć DisplayAttribute w System.Component.DataAnnotations zamiast atrybutu niestandardowego. Twoje wymaganie i tak musi zrobić coś z wyświetlaniem.
źródło
Jeśli jesteś zadowolony z dodatkowej zależności, można to zrobić za pomocą Protobuf-Net Marca Gravella bez martwienia się o najlepszy sposób implementacji odbicia i buforowania itp. Po prostu udekoruj swoje pola,
[ProtoMember]
a następnie uzyskaj dostęp do pól w kolejności numerycznej za pomocą:MetaType metaData = ProtoBuf.Meta.RuntimeTypeModel.Default[typeof(YourTypeName)]; metaData.GetFields();
źródło
Zrobiłem to w ten sposób:
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()); }
z właściwością zadeklarowaną w następujący sposób:
[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_; } } }
źródło
Opierając się na powyższym zaakceptowanym rozwiązaniu, aby uzyskać dokładny indeks, możesz użyć czegoś takiego
Dany
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; } }
Rozszerzenia
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); } }
Stosowanie
var myClass = new MyClass(); var index = myClass.GetOrder(c => c.String2);
Uwaga , nie ma sprawdzania błędów ani tolerancji na błędy, możesz dodać pieprz i sól do smaku
źródło
Inną możliwością jest skorzystanie z
System.ComponentModel.DataAnnotations.DisplayAttribute
Order
nieruchomości. Ponieważ jest wbudowany, nie ma potrzeby tworzenia nowego określonego atrybutu.Następnie wybierz uporządkowane właściwości, takie jak ten
const int defaultOrder = 10000; var properties = type.GetProperties().OrderBy(p => p.FirstAttribute<DisplayAttribute>()?.GetOrder() ?? defaultOrder).ToArray();
I tak można przedstawić klasę
public class Toto { [Display(Name = "Identifier", Order = 2) public int Id { get; set; } [Display(Name = "Description", Order = 1) public string Label {get; set; } }
źródło