web-dev-qa-db-fra.com

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

J'ai besoin d'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, la commande ne peut être garantie lors de l’utilisation de GetProperties()

La méthode GetProperties ne renvoie pas de propriétés dans un .__ particulier. ordre, par exemple ordre alphabétique ou ordre de déclaration.

Mais j'ai lu qu'il existe une solution de contournement en classant les propriétés par la variable MetadataToken. Donc ma question est, est-ce sécuritaire? Je n'arrive pas à trouver des informations sur MSDN à ce sujet. Ou existe-t-il un autre moyen de résoudre ce problème?

Ma mise en œuvre actuelle se présente comme suit:

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

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], en laissant le compilateur insérer l'ordre dans vos propriétés:

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

Et puis 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 gérer des classes partielles, vous pouvez également trier les propriétés à l'aide de [CallerFilePath].

120
ghord

Selon MSDNMetadataToken est unique dans un module - rien ne dit qu'il garantit un ordre quelconque.

MÊME si elle se comportait comme vous le souhaitiez, elle serait spécifique à la mise en œuvre et pourrait changer à tout moment sans préavis.

Voir cette ancienne entrée de blog MSDN .

Je recommanderais vivement de rester à l'écart de toute dépendance vis-à-vis de tels détails de mise en œuvre - 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 que ce soit à un stade très précoce).

12
Yahia

Si vous utilisez la route attributaire, voici une méthode que j'ai utilisée par le passé;

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

Alors utilisez-le comme ça;

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

foreach (var prop in GetSortedProperties<TestRecord>())
{
    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 fonctionnera si vous l'exécutez sur un type sans attribut comparable sur toutes vos propriétés, donc soyez prudent, son utilisation et son utilisation suffiront. 

J'ai laissé de côté la définition de l'ordre: attribut, car il existe un bon échantillon dans le lien de Yahia au message de Marc Gravell.

11

Ce que j'ai testé, le tri par MetadataToken fonctionne.

Certains utilisateurs ici déclarent que cette approche n’est pas bonne ou qu’elle n’est pas fiable, mais je n’ai encore vu aucune preuve de celle-là. Peut-être pouvez-vous poster un bout de code ici quand une approche donnée ne fonctionne pas?

À propos de la compatibilité ascendante - pendant que vous travaillez maintenant sur votre .net 4/.net 4.5 - Microsoft fabrique .net 5 ou une version ultérieure, vous pouvez donc bien supposer que cette méthode de tri ne sera pas interrompue à l'avenir.

Bien sûr, peut-être qu'en 2017, lorsque vous effectuerez la mise à niveau vers .net9, vous rencontrerez des problèmes de compatibilité, mais à ce moment-là, les gars de Microsoft détermineront probablement le "mécanisme de tri officiel". Cela n'a aucun sens de revenir en arrière ou de casser des choses.

Jouer avec des attributs supplémentaires pour le classement des propriétés prend également du temps et de la mise en œuvre - pourquoi s'inquiéter si le tri MetadataToken fonctionne?

4
TarmoPikaro

Vous pouvez utiliser DisplayAttribute dans System.Component.DataAnnotations, au lieu de l'attribut personnalisé. Votre exigence doit faire quelque chose avec l'affichage de toute façon.

1
Panos Roditakis

Je l'ai fait comme ça:

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




    }

avec la propriété déclarée comme suit:

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

}
0
Sentinel

Si vous êtes satisfait de la dépendance supplémentaire, vous pouvez utiliser Protobuf-Net de Marc Gravell sans avoir à vous soucier de la meilleure façon de mettre en œuvre la réflexion et la mise en cache, etc. Décorez simplement vos champs avec [ProtoMember] et puis accédez aux champs par ordre numérique en utilisant:

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

metaData.GetFields();
0
Tom Makin

En vous appuyant sur la solution acceptée ci-dessus, vous pouvez utiliser quelque chose comme ceci pour obtenir l'index exact

Donné  

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

Extensions

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

Utilisation

var myClass = new MyClass();
var index = myClass.GetOrder(c => c.String2);

Remarque , il n'y a pas de vérification d'erreur ni de tolérance de panne, vous pouvez ajouter du poivre et du sel au goût 

0
Michael Randall