106 votes

Obtenir les propriétés dans l'ordre de déclaration en utilisant la réflexion

Je dois obtenir toutes les propriétés en utilisant la réflexion dans l'ordre dans lequel elles sont déclarées dans la classe. Selon MSDN, l'ordre ne peut pas être garanti lorsque l'on utilise GetProperties()

La méthode GetProperties ne retourne pas les propriétés dans un ordre particulier, tel que l'ordre alphabétique ou l'ordre de déclaration.

Mais j'ai lu qu'il existe une solution en ordonnant les propriétés par le MetadataToken. Ma question est donc, est-ce sûr? Je ne trouve pas d'informations à ce sujet sur MSDN. Ou existe-t-il une autre façon de résoudre ce problème?

Mon implémentation actuelle ressemble à ceci:

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

187voto

ghord Points 2842

Sur .net 4.5 (et même .net 4.0 dans vs2012) vous pouvez faire beaucoup mieux avec la réflexion en utilisant une astuce intelligente avec l'attribut [CallerLineNumber], permettant au compilateur d'insérer l'ordre dans vos propriétés pour vous :

[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
{
    //Cela définit le champ order_ avec le numéro de ligne actuel
    [Order]
    public int Property2 { get; set; }

    //Cela définit le champ order_ avec le numéro de ligne actuel
    [Order]
    public int Property1 { get; set; }
}

Ensuite, utilisez la réflexion :

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 vous devez traiter avec des classes partielles, vous pouvez également trier les propriétés en utilisant [CallerFilePath].

20voto

Chris McAtackney Points 2729

Si vous suivez la voie de l'attribut, voici une méthode que j'ai utilisée dans le passé;

public static IOrderedEnumerable GetSortedProperties()
{
  return typeof(T)
    .GetProperties()
    .OrderBy(p => ((Order)p.GetCustomAttributes(typeof(Order), false)[0]).Order);
}

Ensuite, utilisez-le comme ceci;

var test = new TestRecord { A = 1, B = 2, C = 3 };

foreach (var prop in GetSortedProperties())
{
    Console.WriteLine(prop.GetValue(test, null));
}

Où;

class TestRecord
{
    [Order(1)]
    public int A { get; set; }

    [Order(2)]
    public int B { get; set; }

    [Order(3)]
    public int C { get; set; }
}

La méthode renverra une erreur si vous l'exécutez sur un type sans attributs comparables sur toutes vos propriétés évidemment, donc faites attention à la façon dont elle est utilisée et elle devrait être suffisante pour répondre aux besoins.

J'ai omis la définition de Order : Attribute car il y a un bon exemple dans le lien de Yahia vers le post de Marc Gravell.

12voto

Yahia Points 49011

According to MSDN MetadataToken est unique à l'intérieur d'un Module - rien ne garantit un quelconque ordre.

MÊME s'il se comportait comme vous le souhaitez, cela serait spécifique à l'implémentation et pourrait changer à tout moment sans préavis.

Voir ce vieux blog MSDN.

Je recommande fortement de ne pas dépendre de ces détails d'implémentation - voir cette réponse de Marc Gravell.

SI vous avez besoin de quelque chose au moment de la compilation, vous pouvez jeter un œil à Roslyn (bien qu'il soit encore très expérimental).

9voto

labilbe Points 1231

Une autre possibilité est d'utiliser la propriété Order de System.ComponentModel.DataAnnotations.DisplayAttribute. Étant donné qu'elle est intégrée, il n'est pas nécessaire de créer un nouvel attribut spécifique.

Ensuite, sélectionnez les propriétés ordonnées de cette manière

const int defaultOrder = 10000;
var properties = type.GetProperties().OrderBy(p => p.FirstAttribute()?.GetOrder() ?? defaultOrder).ToArray();

Et la classe peut être présentée de cette manière

public class Toto {
    [Display(Name = "Identifiant", Order = 2)
    public int Id { get; set; }

    [Display(Name = "Description", Order = 1)
    public string Label {get; set; }
}

4voto

TarmoPikaro Points 11

Ce que j'ai testé fonctionne en triant par MetadataToken.

Certains utilisateurs ici prétendent que ce n'est pas une bonne approche / pas fiable, mais je n'ai encore vu aucune preuve de cela - peut-être pouvez-vous poster un extrait de code ici lorsque l'approche donnée ne fonctionne pas ?

Concernant la compatibilité ascendante - alors que vous travaillez actuellement sur votre .net 4 / .net 4.5 - Microsoft est en train de développer .net 5 ou une version supérieure, vous pouvez donc probablement supposer que cette méthode de tri ne sera pas cassée à l'avenir.

Bien sûr, peut-être qu'en 2017, lorsque vous passez à .net 9, vous rencontrerez une rupture de compatibilité, mais d'ici là, les gens de Microsoft auront probablement trouvé le "mécanisme de tri officiel". Il n'y a pas de sens à revenir en arrière ou à casser les choses.

Manipuler des attributs supplémentaires pour l'ordre des propriétés prend également du temps et de la mise en œuvre - pourquoi s'embêter si le tri par MetadataToken fonctionne ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X