Получить свойства в порядке объявления, используя отражение - PullRequest
68 голосов
/ 30 января 2012

Мне нужно получить все свойства, используя отражение в том порядке, в котором они объявлены в классе.Согласно MSDN порядок не может быть гарантирован при использовании GetProperties()

Метод GetProperties не возвращает свойства в определенном порядке, например в алфавитном порядке или в порядке объявления.

Но я читал, что есть обходной путь, упорядочив свойства по MetadataToken.Итак, мой вопрос, это безопасно?Я не могу найти какую-либо информацию на MSDN об этом.Или есть какой-то другой способ решения этой проблемы?

Моя текущая реализация выглядит следующим образом:

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

Ответы [ 8 ]

124 голосов
/ 01 августа 2013

В .net 4.5 (и даже .net 4.0 в vs2012) вы можете сделать намного лучше с отражением, используя хитрый трюк с атрибутом [CallerLineNumber], позволяя компилятору вставлять порядок в ваши свойства для вас:

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

А затем используйте отражение:

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

Если вам приходится иметь дело с частичными классами, вы можете дополнительно отсортировать свойства, используя [CallerFilePath].

12 голосов
/ 30 января 2012

Согласно MSDN MetadataToken является уникальным внутри одного модуля - нет ничего, говорящего о том, что он гарантирует какой-либо заказ вообще.

ДАЖЕ, если он вел себя так, как вы этого хотитеэто будет зависеть от реализации и может измениться в любое время без предварительного уведомления.

См. эту старую запись MSDN в блоге .

Я настоятельно рекомендую держаться подальше от любой зависимости от такихдетали реализации - см. этот ответ от Marc Gravell .

Если вам нужно что-то во время компиляции, вы можете взглянуть на Roslyn (хотя это очень раноэтап).

11 голосов
/ 30 января 2012

Если вы идете по маршруту атрибута, вот метод, который я использовал в прошлом;

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

Затем используйте его так:

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

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

Где;

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

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

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

Метод сработает, если вы запустите его с типом без сопоставимых атрибутов для всех ваших свойств, поэтому будьте осторожны с его использованием, и этого должно быть достаточно для требования.

Я упустил определение Порядка: Атрибут, поскольку в ссылке Яхии на пост Марка Гравелла есть хороший пример.

4 голосов
/ 09 марта 2015

То, что я проверил, сортировка MetadataToken работает.

Некоторые пользователи здесь утверждают, что это как-то не очень хороший подход / ненадежный, но я еще не видел никаких доказательств этого - возможно, вы можете опубликоватькакой-нибудь фрагмент кода здесь, когда данный подход не работает?

О обратной совместимости - пока вы сейчас работаете над .net 4 / .net 4.5 - Microsoft делает .net 5 или выше, так что вы в значительной степениМожно предположить, что этот метод сортировки не будет нарушен в будущем.

Конечно, возможно, к 2017 году, когда вы перейдете на .net9, вы столкнетесь с разрывом совместимости, но к тому времени ребята из Microsoft, вероятно, выяснят«официальный механизм сортировки».Нет смысла возвращаться или разбивать вещи.

Игра с дополнительными атрибутами для упорядочения свойств также требует времени и реализации - зачем беспокоиться, если сортировка MetadataToken работает?

1 голос
/ 05 октября 2016

Вы можете использовать DisplayAttribute в System.Component.DataAnnotations вместо пользовательского атрибута. Ваше требование все равно должно что-то делать с дисплеем.

0 голосов
/ 24 декабря 2018

Основываясь на принятом выше решении, чтобы получить точный индекс, вы можете использовать что-то вроде этого

Учитывая

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

Расширения

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

Использование

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

Примечание , нет проверки ошибок или отказоустойчивости, вы можете добавить перец исоль по вкусу

0 голосов
/ 11 июня 2018

Я сделал это так:

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




    }

с объявленным имуществом:

  [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 голосов
/ 14 декабря 2012

Если вы довольны дополнительной зависимостью, для этого можно использовать Protobuf-Net Марка Гравелла, не беспокоясь о том, как лучше всего реализовать отражение, кэширование и т. Д. Просто украсьте свои поля, используя [ProtoMember], а затем получить доступ к полям в числовом порядке, используя:

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

metaData.GetFields();
...