Вот мое решение, оно немного затянуто, потому что, как указано в комментариях, порядок результата GetProperties
не гарантируется.В .NET 4.5+ есть способ обойти это, как описано в этом ответе .
Итак, первая часть - убедиться, что мы можем получить свойства класса и его базового класса.(е) в предсказуемом порядке, независимо от нашего пользовательского заказа.Для этого мы возвращаемся из базового класса вверх и собираем свойства, объявленные в каждом, сортируя результаты по порядку, в котором свойство появляется в классе.
public static IEnumerable<PropertyMetaData> GetPropertiesOrdered(Type someType, int inheritanceLevel = 1)
{
List<PropertyMetaData> seenProperties = new List<PropertyMetaData>();
if (someType.BaseType != (typeof(object)))
seenProperties.AddRange(GetPropertiesOrdered(someType.BaseType, inheritanceLevel + 1));
var properties = someType
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Select(a => new PropertyMetaData(a, inheritanceLevel))
.Where(a => a.AttributeData != null);
properties = properties
.OrderBy(a => a.AttributeData.ClassOrder)
.Select((a, ordinal) =>
{
a.OrderWithinClass = ordinal + 1;
return a;
});
return seenProperties
.Union(properties)
.OrderByDescending(a => a.InheritanceLevel)
.ThenBy(a => a.OrderWithinClass)
.Select((a, ordinal) =>
{
a.OrderOverall = ordinal + 1;
return a;
});
}
Для поддержки этого атрибутизменяется немного, как показано ниже, для использования атрибута номера строки компилятора, как в связанном ответе:
public class CustomAttribute : OrderAttribute
{
public CustomAttribute([CallerLineNumber]int order = 0) : base(order)
{
}
public string Name { get; set; }
public bool Ignore { get; set; }
public override int Order { get; set; } = -1;
}
public abstract class OrderAttribute : Attribute
{
private readonly int _classOrder;
public OrderAttribute([CallerLineNumber]int order = 0)
{
_classOrder = order;
}
public int ClassOrder { get { return _classOrder; } }
public virtual int Order { get; set; }
}
Итак, теперь мы получили существующие свойства в предсказуемом, пронумерованном порядке, начиная с базового класса Person
свойства затем следуют верхний уровень Student
свойства класса.Выходные данные метода GetPropertiesOrdered
представляют собой контейнерный класс с информацией о порядке встретившихся свойств, настраиваемом атрибуте, связанном с каждым, и коде для обработки настраиваемой сортировки - то есть, если порядок определен, то предпочтительнее егопорядок свойств, видимых в типе.
public class PropertyMetaData : IComparable<PropertyMetaData>
{
public PropertyMetaData(PropertyInfo propertyInfo, int inheritanceLevel)
{
InheritanceLevel = inheritanceLevel;
PropertyInfo = propertyInfo;
AttributeData = propertyInfo.GetCustomAttribute<CustomAttribute>();
}
public int InheritanceLevel { get; set; }
public int OrderWithinClass { get; set; }
public int OrderOverall { get; set; }
public CustomAttribute AttributeData { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public int GetOrder()
{
return HasCustomOrder() ? AttributeData.Order : this.OrderOverall;
}
public bool HasCustomOrder()
{
return AttributeData.Order != -1;
}
public int CompareTo(PropertyMetaData other)
{
var myOrder = GetOrder();
var otherOrder = other.GetOrder();
int compare = myOrder.CompareTo(otherOrder);
if (compare != 0 || other == this) return compare;
if (HasCustomOrder() && other.HasCustomOrder()) return 0;
if (HasCustomOrder() && !other.HasCustomOrder()) return -1;
return 1;
}
}
В целом, его можно назвать так:
var propertiesSorted =
GetPropertiesOrdered(typeof(Student))
.OrderBy(a => a);
, который возвращает нам поля в требуемом порядке для вашего примера.: https://dotnetfiddle.net/dd5hVN