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

1voto

Panos Roditakis Points 79

Vous pouvez utiliser DisplayAttribute dans System.Component.DataAnnotations, plutôt qu'un attribut personnalisé. Votre exigence a de toute façon quelque chose à voir avec l'affichage.

1voto

Carl Walsh Points 417

Si vous pouvez appliquer une disposition mémoire connue à votre type, vous pouvez compter sur StructLayout(LayoutKind.Sequential) puis trier par les décalages des champs en mémoire.

Ainsi, vous n'avez pas besoin de quelconque attribut sur chaque champ du type.

Cependant, cela comporte certains inconvénients sérieux :

  • Tous les types de champs doivent avoir une représentation en mémoire (pratiquement aucun autre type de référence que des tableaux de longueur fixe ou des chaînes). Cela inclut les types parent, même si vous ne souhaitez trier que les champs du type enfant.
  • Vous pouvez utiliser ceci pour les classes, y compris l'héritage, mais toutes les classes parent doivent également avoir une disposition séquentielle définie.
  • Évidemment, cela ne trie pas les propriétés, mais les champs peuvent convenir pour les POCOs.

    [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)] // taille importe peu 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}"); } } }

Résultats :

Base: System.Int32
TestStruct: TestStruct
A: System.Boolean
B: System.String
C: System.DateTime
D: System.Byte[]

Si vous essayez d'ajouter des types de champs interdits, vous obtiendrez System.ArgumentException : Le type 'TestRecord' ne peut pas être traité en tant que structure non gérée ; aucune taille ou décalage significatif ne peut être calculé.

0voto

shakinfree Points 548

Si vous êtes satisfait de la dépendance supplémentaire, vous pouvez utiliser Protobuf-Net de Marc Gravell pour le faire sans avoir à vous soucier de la meilleure façon de mettre en œuvre la réflexion et le caching, etc. Il vous suffit de décorer vos champs en utilisant [ProtoMember] puis d'accéder aux champs dans l'ordre numérique en utilisant :

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

metaData.GetFields();

0voto

Sentinel Points 528

J'ai fait de cette façon :

internal static IEnumerable> TypeHierarchy(this Type type)
{
    var ct = type;
    var cl = 0;
    while (ct != null)
    {
        yield return new Tuple(cl,ct);
        ct = ct.BaseType;
        cl++;
    }
}

internal class PropertyInfoComparer : EqualityComparer
{
    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 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(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_; } }

}

0voto

TheGeneral Points 40470

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

Étant 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(this T Class, Expression> propertySelector)
   {
      var body = (MemberExpression)propertySelector.Body;
      var propertyInfo = (PropertyInfo)body.Member;
      return propertyInfo.Order();
   }

   public static int Order(this PropertyInfo propertyInfo)
   {
      return typeof(T).GetProperties()
                      .Where(property => Attribute.IsDefined(property, typeof(OrderAttribute)))
                      .OrderBy(property => property.GetCustomAttributes().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'erreurs ou de tolérance aux pannes, vous pouvez ajouter du poivre et du sel selon votre goût

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