Necesito obtener todas las propiedades usando la reflexión en el orden en que se declaran en la clase. Según MSDN, no se puede garantizar el pedido al usarGetProperties()
El método GetProperties no devuelve propiedades en un orden particular, como orden alfabético o de declaración.
Pero he leído que hay una solución al ordenar las propiedades por MetadataToken
. Entonces mi pregunta es, ¿es seguro? Parece que no puedo encontrar ninguna información en MSDN al respecto. ¿O hay alguna otra forma de solucionar este problema?
Mi implementación actual tiene el siguiente aspecto:
var props = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(x => x.MetadataToken);
c#
reflection
properties
getproperties
Magnus
fuente
fuente
Respuestas:
En .net 4.5 (e incluso .net 4.0 en vs2012) puede hacerlo mucho mejor con la reflexión utilizando un truco inteligente con
[CallerLineNumber]
atributo, permitiendo que el compilador inserte el orden en sus propiedades por usted:[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; } }
Y luego usa la reflexión:
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) { // }
Si tiene que lidiar con clases parciales, también puede ordenar las propiedades usando
[CallerFilePath]
.fuente
CsvColumnAttribute
esto comoFieldIndex
valor predeterminadoSi va por la ruta de los atributos, aquí hay un método que he usado en el pasado;
public static IOrderedEnumerable<PropertyInfo> GetSortedProperties<T>() { return typeof(T) .GetProperties() .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order); }
Entonces utilícelo así;
var test = new TestRecord { A = 1, B = 2, C = 3 }; foreach (var prop in GetSortedProperties<TestRecord>()) { Console.WriteLine(prop.GetValue(test, null)); }
Dónde;
class TestRecord { [Order(1)] public int A { get; set; } [Order(2)] public int B { get; set; } [Order(3)] public int C { get; set; } }
El método fallará si lo ejecuta en un tipo sin atributos comparables en todas sus propiedades, obviamente, así que tenga cuidado con cómo se usa y debería ser suficiente para los requisitos.
He omitido la definición de Orden: atributo, ya que hay una buena muestra en el enlace de Yahia a la publicación de Marc Gravell.
fuente
Según MSDN,
MetadataToken
es único dentro de un módulo: no hay nada que diga que garantice ningún pedido.INCLUSO si se comportara de la forma deseada, sería específico de la implementación y podría cambiar en cualquier momento sin previo aviso.
Vea esta antigua entrada de blog de MSDN .
Recomendaría encarecidamente que se mantenga alejado de cualquier dependencia de dichos detalles de implementación; consulte esta respuesta de Marc Gravell .
SI necesita algo en tiempo de compilación, puede echar un vistazo a Roslyn (aunque está en una etapa muy temprana).
fuente
Lo que he probado en la clasificación por MetadataToken funciona.
Algunos de los usuarios aquí afirman que este de alguna manera no es un buen enfoque / no es confiable, pero aún no he visto ninguna evidencia de eso, ¿tal vez pueda publicar algún fragmento de código aquí cuando el enfoque dado no funcione?
Acerca de la compatibilidad con versiones anteriores, mientras que ahora está trabajando en su .net 4 / .net 4.5, Microsoft está haciendo .net 5 o superior, por lo que puede asumir que este método de clasificación no se romperá en el futuro.
Por supuesto, tal vez en 2017, cuando se actualice a .net9, se produzca una ruptura de compatibilidad, pero para ese momento, los chicos de Microsoft probablemente descubrirán el "mecanismo de clasificación oficial". No tiene sentido retroceder o romper cosas.
Jugar con atributos adicionales para la ordenación de propiedades también requiere tiempo e implementación: ¿por qué molestarse si la ordenación de MetadataToken funciona?
fuente
GetProperties()
. Su argumento de por qué puede confiar en el orden es exactamente el mismo que el argumento de por qué no puede confiar en que las versiones futuras de .net no cambien este comportamiento: cada nueva versión es libre de cambiar los detalles de implementación. Ahora, .net en realidad sigue la filosofía de que “los errores son características”, por lo que en realidad probablemente nunca cambiarían el orden deGetProperties()
. Es solo que la API dice que pueden hacerlo.Puede utilizar DisplayAttribute en System.Component.DataAnnotations, en lugar de un atributo personalizado. De todos modos, su requisito tiene que ver con la pantalla.
fuente
Si está satisfecho con la dependencia adicional, Protobuf-Net de Marc Gravell se puede usar para hacer esto sin tener que preocuparse por la mejor manera de implementar la reflexión y el almacenamiento en caché, etc. Simplemente decore sus campos usando
[ProtoMember]
y luego acceda a los campos en orden numérico usando:MetaType metaData = ProtoBuf.Meta.RuntimeTypeModel.Default[typeof(YourTypeName)]; metaData.GetFields();
fuente
Lo hice de esta manera:
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()); }
con la propiedad declarada de la siguiente manera:
[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_; } } }
fuente
Sobre la base de la solución aceptada anteriormente, para obtener el índice exacto, podría usar algo como esto
Dado
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; } }
Extensiones
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); } }
Uso
var myClass = new MyClass(); var index = myClass.GetOrder(c => c.String2);
Tenga en cuenta que no hay verificación de errores ni tolerancia a fallas, puede agregar pimienta y sal al gusto
fuente
Otra posibilidad es utilizar la
System.ComponentModel.DataAnnotations.DisplayAttribute
Order
propiedad. Dado que está integrado, no es necesario crear un nuevo atributo específico.Luego seleccione propiedades ordenadas como esta
const int defaultOrder = 10000; var properties = type.GetProperties().OrderBy(p => p.FirstAttribute<DisplayAttribute>()?.GetOrder() ?? defaultOrder).ToArray();
Y la clase se puede presentar así
public class Toto { [Display(Name = "Identifier", Order = 2) public int Id { get; set; } [Display(Name = "Description", Order = 1) public string Label {get; set; } }
fuente