it-swarm.com.de

Rufen Sie die Eigenschaften in der Reihenfolge der Deklaration mithilfe der Reflektion ab

Ich muss alle Eigenschaften mit Reflektion in der Reihenfolge abrufen, in der sie in der Klasse deklariert sind. Laut MSDN kann die Reihenfolge bei Verwendung von GetProperties() nicht garantiert werden

Die GetProperties-Methode gibt keine Eigenschaften in einem bestimmten .__ zurück. Reihenfolge, wie alphabetische oder Deklarationsreihenfolge.

Ich habe jedoch gelesen, dass es eine Problemumgehung gibt, indem die Eigenschaften nach MetadataToken sortiert werden. Meine Frage ist also, ist das sicher? Ich kann keine Informationen über MSDN darüber finden. Oder gibt es eine andere Möglichkeit, dieses Problem zu lösen?

Meine aktuelle Implementierung sieht wie folgt aus:

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

Auf .net 4.5 (und sogar .net 4.0 in vs2012) können Sie die Reflektion mit einem intelligenten Trick mit [CallerLineNumber]-Attribut viel besser machen, indem der Compiler die Reihenfolge in Ihre Eigenschaften einfügt

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

Und dann Reflexion verwenden:

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)
{
   //
}

Wenn Sie mit Teilklassen arbeiten müssen, können Sie die Eigenschaften zusätzlich mit [CallerFilePath] sortieren.

120
ghord

Laut MSDNMetadataToken ist innerhalb eines Moduls einzigartig - nichts sagt, dass es irgendeine Reihenfolge garantiert.

Selbst wenn es sich so verhalten würde, wie Sie es sich wünschen, wäre das implementierungsspezifisch und könnte sich jederzeit ohne Vorankündigung ändern.

Siehe diesen alten MSDN-Blogeintrag .

Ich würde nachdrücklich empfehlen, sich nicht von solchen Implementierungsdetails abhängig zu machen - siehe diese Antwort von Marc Gravell .

WENN Sie zur Kompilierzeit etwas benötigen, können Sie einen Blick auf Roslyn werfen (obwohl es sich noch in einem sehr frühen Stadium befindet).

12
Yahia

Wenn Sie die Attributroute wählen, haben Sie eine Methode, die ich in der Vergangenheit verwendet habe.

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

Dann benutze es so;

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

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

Woher;

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

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

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

Die Methode kann nicht verwendet werden, wenn Sie sie für einen Typ ohne vergleichbare Attribute für alle Ihre Eigenschaften ausführen. Achten Sie daher darauf, wie sie verwendet werden, und es sollte für die Anforderungen ausreichend sein. 

Ich habe die Definition von Order: Attribute weggelassen, da Yahias Link zu Marc Gravells Post ein gutes Beispiel enthält.

Was ich getestet habe, funktioniert die Sortierung nach MetadataToken.

Einige Benutzer behaupten, dies sei irgendwie nicht gut/nicht zuverlässig, aber ich habe noch keine Beweise dafür gesehen - vielleicht können Sie hier Code-Snipet posten, wenn der angegebene Ansatz nicht funktioniert?

Informationen zur Abwärtskompatibilität - während Sie gerade an .net 4/.net 4.5 arbeiten, stellt Microsoft .net 5 oder höher her. Sie können also davon ausgehen, dass diese Sortiermethode in Zukunft nicht beschädigt wird.

Natürlich werden Sie bis 2017, wenn Sie ein Upgrade auf .net9 durchführen werden, die Kompatibilitätspause treffen, aber zu diesem Zeitpunkt werden Microsoft-Leute wahrscheinlich den "offiziellen Sortiermechanismus" herausfinden. Es macht keinen Sinn, zurück zu gehen oder Dinge zu zerbrechen.

Das Spiel mit zusätzlichen Attributen für die Eigenschaftsbestellung erfordert auch Zeit und Implementierung. Warum sollten Sie sich die Mühe machen, wenn die MetadataToken-Sortierung funktioniert?

4
TarmoPikaro

Sie können DisplayAttribute anstelle von benutzerdefinierten Attributen in System.Component.DataAnnotations verwenden. Ihre Anforderung hat sowieso etwas mit Anzeige zu tun.

1
Panos Roditakis

Ich habe es so gemacht:

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




    }

mit der Eigenschaft wie folgt deklariert:

  [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

Wenn Sie mit der zusätzlichen Abhängigkeit zufrieden sind, kann Marc Gravells Protobuf-Net dazu verwendet werden, ohne sich Gedanken darüber machen zu müssen, wie Reflection und Caching implementiert werden können. Dekorieren Sie Ihre Felder einfach mit [ProtoMember] und Rufen Sie dann die Felder in numerischer Reihenfolge auf:

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

metaData.GetFields();
0
Tom Makin

Aufbauend auf der oben genannten akzeptierten Lösung können Sie so etwas wie den folgenden Index verwenden

Gegeben  

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

Erweiterungen

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

Verwendungszweck

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

Hinweis es gibt keine Fehlerprüfung oder Fehlertoleranz, Sie können Pfeffer und Salz nach Belieben hinzufügen 

0
Michael Randall